From f4aa9d2976b5add0b7595be74a7d80de2d20cf90 Mon Sep 17 00:00:00 2001 From: sam Date: Tue, 30 Jul 2024 21:42:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=94=9F=E6=88=90=20RAW=20=E5=9B=BE?= =?UTF-8?q?=E5=83=8F=E7=9A=84=E7=BC=A9=E7=95=A5=E5=9B=BE=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E6=97=8B=E8=BD=AC=E7=BC=A9=E7=95=A5=E5=9B=BE=E7=9A=84=E6=96=B9?= =?UTF-8?q?=E5=90=91=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/thumb/libraw.go | 176 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 173 insertions(+), 3 deletions(-) diff --git a/pkg/thumb/libraw.go b/pkg/thumb/libraw.go index ad168d974d..6f33702031 100644 --- a/pkg/thumb/libraw.go +++ b/pkg/thumb/libraw.go @@ -3,7 +3,11 @@ package thumb import ( "bytes" "context" + "errors" "fmt" + "image" + "image/jpeg" + "image/png" "io" "os" "os/exec" @@ -72,14 +76,180 @@ func (f *LibRawGenerator) Generate(ctx context.Context, file io.Reader, _ string outputFilePath := inputFilePath + ".thumb.jpg" defer func() { _ = os.Remove(outputFilePath) }() - // use builtin function - ff, err := os.OpenFile(outputFilePath, os.O_RDONLY, os.ModePerm) + ff, err := os.Open(outputFilePath) if err != nil { return nil, fmt.Errorf("failed to open temp file: %w", err) } defer func() { _ = ff.Close() }() - return new(Builtin).Generate(ctx, ff, outputFilePath, filepath.Base(outputFilePath), options) + // use builtin generator + result, err := new(Builtin).Generate(ctx, ff, outputFilePath, filepath.Base(outputFilePath), options) + if err != nil { + return nil, fmt.Errorf("failed to generate thumbnail: %w", err) + } + + orientation, err := getJpegOrientation(outputFilePath) + if err != nil { + return nil, fmt.Errorf("failed to get jpeg orientation: %w", err) + } + if orientation == 1 { + return result, nil + } + + if err = rotateImg(result.Path, orientation); err != nil { + return nil, fmt.Errorf("failed to rotate image: %w", err) + } + return result, nil +} + +func rotateImg(filePath string, orientation int) error { + resultImg, err := os.OpenFile(filePath, os.O_RDWR, 0777) + if err != nil { + return err + } + defer func() { _ = resultImg.Close() }() + + imgFlag := make([]byte, 3) + if _, err = io.ReadFull(resultImg, imgFlag); err != nil { + return err + } + if _, err = resultImg.Seek(0, 0); err != nil { + return err + } + + var img image.Image + if bytes.Equal(imgFlag, []byte{0xFF, 0xD8, 0xFF}) { + img, err = jpeg.Decode(resultImg) + } else { + img, err = png.Decode(resultImg) + } + if err != nil { + return err + } + + switch orientation { + case 8: + img = rotate90(img) + case 3: + img = rotate90(rotate90(img)) + case 6: + img = rotate90(rotate90(rotate90(img))) + } + + if err = resultImg.Truncate(0); err != nil { + return err + } + if _, err = resultImg.Seek(0, 0); err != nil { + return err + } + + if bytes.Equal(imgFlag, []byte{0xFF, 0xD8, 0xFF}) { + return jpeg.Encode(resultImg, img, nil) + } + return png.Encode(resultImg, img) +} + +func getJpegOrientation(fileName string) (int, error) { + f, err := os.Open(fileName) + if err != nil { + return 0, err + } + defer func() { _ = f.Close() }() + + header := make([]byte, 6) + defer func() { header = nil }() + if _, err = io.ReadFull(f, header); err != nil { + return 0, err + } + + // jpeg format header + if !bytes.Equal(header[:3], []byte{0xFF, 0xD8, 0xFF}) { + return 0, errors.New("not a jpeg") + } + + // not a APP1 marker + if header[3] != 0xE1 { + return 1, nil + } + + // exif data total length + totalLen := int(header[4])<<8 + int(header[5]) - 2 + buf := make([]byte, totalLen) + defer func() { buf = nil }() + if _, err = io.ReadFull(f, buf); err != nil { + return 0, err + } + + // remove Exif identifier code + buf = buf[6:] + + // byte order + parse16, parse32, err := initParseMethod(buf[:2]) + if err != nil { + return 0, err + } + + // version + _ = buf[2:4] + + // first IFD offset + offset := parse32(buf[4:8]) + + // first DE offset + offset += 2 + buf = buf[offset:] + + const ( + orientationTag = 0x112 + deEntryLength = 12 + ) + for len(buf) > deEntryLength { + tag := parse16(buf[:2]) + if tag == orientationTag { + return int(parse32(buf[8:12])), nil + } + buf = buf[deEntryLength:] + } + + return 0, errors.New("orientation not found") +} + +func initParseMethod(buf []byte) (func([]byte) int16, func([]byte) int32, error) { + if bytes.Equal(buf, []byte{0x49, 0x49}) { + return littleEndian16, littleEndian32, nil + } + if bytes.Equal(buf, []byte{0x4D, 0x4D}) { + return bigEndian16, bigEndian32, nil + } + return nil, nil, errors.New("invalid byte order") +} + +func littleEndian16(buf []byte) int16 { + return int16(buf[0]) | int16(buf[1])<<8 +} + +func bigEndian16(buf []byte) int16 { + return int16(buf[1]) | int16(buf[0])<<8 +} + +func littleEndian32(buf []byte) int32 { + return int32(buf[0]) | int32(buf[1])<<8 | int32(buf[2])<<16 | int32(buf[3])<<24 +} + +func bigEndian32(buf []byte) int32 { + return int32(buf[3]) | int32(buf[2])<<8 | int32(buf[1])<<16 | int32(buf[0])<<24 +} + +func rotate90(img image.Image) image.Image { + bounds := img.Bounds() + width, height := bounds.Dx(), bounds.Dy() + newImg := image.NewRGBA(image.Rect(0, 0, height, width)) + for x := 0; x < width; x++ { + for y := 0; y < height; y++ { + newImg.Set(y, width-x-1, img.At(x, y)) + } + } + return newImg } func (f *LibRawGenerator) Priority() int {