源码分享-golang的BMP文件读写库

用于读写BMP文件的golang版源码

源码基于源码分享-golang的二进制文件读写库 https://blog.csdn.net/zhyulo/article/details/128890546

BMP文件格式可参考位图文件解析-位图(bmp)、图标(ico)与光标(cur) https://blog.csdn.net/zhyulo/article/details/85934728

import (
	"binary"
	"bufio"
	"errors"
	"fmt"
	"os"
)

var ofb = errors.New("out of bound")

type BitmapFile struct {
	Header   BitmapFileHeader
	Info     BitmapInfoHeader
	Quad     BitmapQuad  `info:"调色板"`
	BitLines BitmapLines `info:"点阵数据"`
}

func ReadBitmapFile(path string) (*BitmapFile, error) {
	buf, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	var bmp BitmapFile
	err = binary.Unmarshal(buf, false, &bmp)
	return &bmp, err
}

func NewBitmapFile(width, height int32, bitCount uint16) *BitmapFile {
	bmp := &BitmapFile{
		Header: BitmapFileHeader{Type: MagicBmp{'B', 'M'}},
		Info: BitmapInfoHeader{
			Size:     40,
			Width:    width,
			Height:   height,
			Planes:   1,
			BitCount: bitCount,
		},
	}
	if height < 0 {
		height = -height
	}
	bmp.BitLines = make(BitmapLines, height, height)
	bytePerLine := (int32(bitCount)*width + 31) / 32 * 4
	for i := int32(0); i < height; i++ {
		bmp.BitLines[i] = make(BitmapLine, bytePerLine, bytePerLine)
	}
	bmp.Info.SizeImage = uint32(bytePerLine * height)
	if bitCount < 16 {
		bmp.Info.ClrUsed = 1 << bitCount
		bmp.Quad = make(BitmapQuad, bmp.Info.ClrUsed, bmp.Info.ClrUsed)
	}
	bmp.Header.OffBits = 14 + bmp.Info.Size + uint32(len(bmp.Quad))*4
	bmp.Header.Size = bmp.Header.OffBits + bmp.Info.SizeImage

	return bmp
}

func (v *BitmapFile) SetQuad(f func(index int) RgbQuad) {
	for i := 0; i < len(v.Quad); i++ {
		v.Quad[i] = f(i)
	}
}

func (v *BitmapFile) ReadLine(y int) ([]int, error) {
	height := v.Info.Height
	if height < 0 {
		height = -height
	}
	if y < 0 || y >= int(height) {
		return nil, ofb
	}

	val := make([]int, v.Info.Width, v.Info.Height)
	err := binary.NewDecoder(v.BitLines[y], 0).Decode(val, true, int(v.Info.BitCount))
	return val, err
}

func (v *BitmapFile) WriteLine(y int, val any) error {
	height := v.Info.Height
	if height < 0 {
		height = -height
	}
	if y < 0 || y >= int(height) {
		return ofb
	}

	w := &lineWriter{buf: v.BitLines[y]}
	enc := binary.NewEncoder(w)
	defer enc.Write(nil)
	return enc.Encode(val, true, int(v.Info.BitCount))
}

func (v *BitmapFile) Save(path string) error {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()
	buf := bufio.NewWriter(f)
	defer buf.Flush()

	enc := binary.NewEncoder(buf)
	defer enc.Write(nil)
	return enc.Encode(v, false, 0)
}

type BitmapFileHeader struct {
	Type      MagicBmp `info:"文件标识"`
	Size      uint32   `info:"文件大小"`
	Reserved1 uint16
	Reserved2 uint16
	OffBits   uint32 `info:"点阵数据偏移"`
}

type MagicBmp [2]byte

func (v *MagicBmp) UnmarshalBinary(dec *binary.Decoder, isBig bool, bit int) error {
	err := dec.Decode(v[:], isBig, bit)
	if err != nil {
		return err
	}
	if string(v[:]) != "BM" {
		return errors.New("file isn't bitmap format")
	}
	return nil
}

type BitmapInfoHeader BitmapInfo
type BitmapInfo struct {
	Size          uint32    `info:"信息头大小"`
	Width         int32     `info:"宽度(像素)"`
	Height        int32     `info:"高度(像素)"`
	Planes        uint16    `info:"颜色平面数"`
	BitCount      uint16    `info:"比特/像素"`
	Compression   uint32    `info:"压缩类型"`
	SizeImage     uint32    `info:"点阵数据大小"`
	XPelsPerMeter int32     `info:"水平分辨率(像素/米)"`
	YPelsPerMeter int32     `info:"垂直分辨率(像素/米)"`
	ClrUsed       uint32    `info:"调色板颜色数"`
	ClrImportant  uint32    `info:"关键颜色数"`
	ClrMask       ColorMask `info:"颜色掩码"`
}

func (v *BitmapInfoHeader) UnmarshalBinary(dec *binary.Decoder, isBig bool, bit int) error {
	dec.SetArg(v)
	return dec.Decode((*BitmapInfo)(v), isBig, bit)
}

func (v *BitmapInfoHeader) MarshalBinary(enc *binary.Encoder, isBig bool, bit int) error {
	enc.SetArg(v)
	return enc.Encode((*BitmapInfo)(v), isBig, bit)
}

type ColorMask [3]uint32

func (v *ColorMask) UnmarshalBinary(dec *binary.Decoder, isBig bool, bit int) error {
	bmp, ok := dec.Arg().(*BitmapInfoHeader)
	if !ok || bmp == nil {
		return errors.New("decode arg not set")
	}

	if bmp.Size == 40 {
		return nil
	} else if bmp.Size != 52 {
		return fmt.Errorf("unsupport BitmapInfoHeader size: %d", bmp.Size)
	}
	return dec.Decode(v[:], isBig, bit)
}

func (v *ColorMask) MarshalBinary(enc *binary.Encoder, isBig bool, bit int) error {
	bmp, ok := enc.Arg().(*BitmapInfoHeader)
	if !ok || bmp == nil {
		return errors.New("encode arg not set")
	}

	if bmp.Size == 40 {
		return nil
	} else if bmp.Size != 52 {
		return fmt.Errorf("unsupport BitmapInfoHeader size: %d", bmp.Size)
	}
	return enc.Encode(v[:], isBig, bit)
}

type BitmapQuad []RgbQuad
type RgbQuad struct {
	Blue     byte
	Green    byte
	Red      byte
	Reserved byte
}

func (v *BitmapQuad) UnmarshalBinary(dec *binary.Decoder, isBig bool, bit int) error {
	bmp, ok := dec.Arg().(*BitmapInfoHeader)
	if !ok || bmp == nil {
		return errors.New("decode arg not set")
	}

	if bmp.BitCount >= 16 {
		return nil
	}
	num := bmp.ClrUsed
	if num == 0 {
		num = 1 << bmp.BitCount
	}
	*v = make(BitmapQuad, num, num)
	return dec.Decode((*v)[:], isBig, bit)
}

type BitmapLines []BitmapLine
type BitmapLine []byte

func (v *BitmapLines) UnmarshalBinary(dec *binary.Decoder, isBig bool, bit int) error {
	bmp, ok := dec.Arg().(*BitmapInfoHeader)
	if !ok || bmp == nil {
		return errors.New("decode arg not set")
	}

	bytePerLine := int((int32(bmp.BitCount)*bmp.Width + 31) / 32 * 4)
	lineNum := int(bmp.Height)
	if lineNum < 0 {
		lineNum = -lineNum
	}

	*v = make([]BitmapLine, lineNum)
	for i := 0; i < lineNum; i++ {
		newDec := dec.SubDecoder(bytePerLine)
		err := newDec.Decode(&(*v)[i], isBig, bit)
		if err != nil {
			return err
		}
		err = dec.Seek(newDec.Pos())
		if err != nil {
			return err
		}
	}
	return nil
}

type lineWriter struct {
	buf []byte
	oft int
}

func (w *lineWriter) Write(p []byte) (n int, err error) {
	n = len(p)
	if w.oft+n > len(w.buf) {
		n = len(w.buf) - w.oft
		err = ofb
	}
	copy(w.buf[w.oft:], p)
	w.oft += n
	return
}

你可能感兴趣的:(GoLang,golang,BMP)