Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion internal/providers/gofpdf/parseimage.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package gofpdf

import (
"bytes"
"errors"
"image"
"image/draw"
"image/png"

"github.com/johnfercher/maroto/v2/pkg/consts/extension"
"github.com/johnfercher/maroto/v2/pkg/core/entity"
Expand All @@ -14,8 +18,51 @@ func FromBytes(bytes []byte, ext extension.Type) (*entity.Image, error) {
return nil, ErrInvalidImageFormat
}

data := bytes
if ext == extension.Png {
var err error
data, err = normalizePNGBytes(bytes)
if err != nil {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return nil, err
}
}

return &entity.Image{
Bytes: bytes,
Bytes: data,
Extension: ext,
}, nil
}

func pngBitDepth(data []byte) (int, bool) {
const pngSignature = "\x89PNG\r\n\x1a\n"
if len(data) < 26 || string(data[:8]) != pngSignature {
return 0, false
}
if string(data[12:16]) != "IHDR" {
return 0, false
}
return int(data[24]), true
}

func normalizePNGBytes(data []byte) ([]byte, error) {
depth, ok := pngBitDepth(data)
if !ok || depth != 16 {
return data, nil
}

src, err := png.Decode(bytes.NewReader(data))
if err != nil {
return data, nil
}

b := src.Bounds()
dst := image.NewNRGBA(b)
draw.Draw(dst, b, src, b.Min, draw.Src)

var buf bytes.Buffer
if err := png.Encode(&buf, dst); err != nil {
return nil, err
}

return buf.Bytes(), nil
}
97 changes: 95 additions & 2 deletions internal/providers/gofpdf/parseimage_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package gofpdf_test

import (
"bytes"
"image"
"image/color"
"image/png"
"testing"

"github.com/johnfercher/maroto/v2/internal/providers/gofpdf"

"github.com/johnfercher/maroto/v2/pkg/consts/extension"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestFromBytes(t *testing.T) {
Expand All @@ -20,7 +24,7 @@ func TestFromBytes(t *testing.T) {
assert.Nil(t, img)
assert.NotNil(t, err)
})
t.Run("when extension is not valid, should return error", func(t *testing.T) {
t.Run("when extension is valid, should return image", func(t *testing.T) {
t.Parallel()
// Act
img, err := gofpdf.FromBytes([]byte{1, 2, 3}, extension.Jpg)
Expand All @@ -30,3 +34,92 @@ func TestFromBytes(t *testing.T) {
assert.Nil(t, err)
})
}

func TestFromBytes_Normalizes16BitPNG(t *testing.T) {
t.Parallel()

cases := []struct {
name string
build func() image.Image
assertPx func(t *testing.T, decoded image.Image)
}{
{
name: "NRGBA64",
build: func() image.Image {
m := image.NewNRGBA64(image.Rect(0, 0, 1, 1))
m.SetNRGBA64(0, 0, color.NRGBA64{R: 65535, G: 32768, B: 16384, A: 65535})
return m
},
assertPx: func(t *testing.T, decoded image.Image) {
t.Helper()
assert.Equal(t, image.Rect(0, 0, 1, 1), decoded.Bounds())
c := color.NRGBAModel.Convert(decoded.At(0, 0)).(color.NRGBA)
assert.Equal(t, color.NRGBA{R: 255, G: 128, B: 64, A: 255}, c)
},
},
{
name: "RGBA64",
build: func() image.Image {
m := image.NewRGBA64(image.Rect(0, 0, 1, 1))
m.SetRGBA64(0, 0, color.RGBA64{R: 65535, G: 32768, B: 16384, A: 65535})
return m
},
assertPx: func(t *testing.T, decoded image.Image) {
t.Helper()
assert.Equal(t, image.Rect(0, 0, 1, 1), decoded.Bounds())
c := color.NRGBAModel.Convert(decoded.At(0, 0)).(color.NRGBA)
assert.Equal(t, color.NRGBA{R: 255, G: 128, B: 64, A: 255}, c)
},
},
{
name: "Gray16",
build: func() image.Image {
m := image.NewGray16(image.Rect(0, 0, 1, 1))
m.SetGray16(0, 0, color.Gray16{Y: 32768})
return m
},
assertPx: func(t *testing.T, decoded image.Image) {
t.Helper()
assert.Equal(t, image.Rect(0, 0, 1, 1), decoded.Bounds())
c := color.NRGBAModel.Convert(decoded.At(0, 0)).(color.NRGBA)
assert.Equal(t, color.NRGBA{R: 128, G: 128, B: 128, A: 255}, c)
},
},
}

for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

var input bytes.Buffer
require.NoError(t, png.Encode(&input, tc.build()))

img, err := gofpdf.FromBytes(input.Bytes(), extension.Png)
require.NoError(t, err)

decoded, err := png.Decode(bytes.NewReader(img.Bytes))
require.NoError(t, err)
switch decoded.(type) {
case *image.NRGBA64, *image.RGBA64, *image.Gray16:
t.Fatalf("expected 8-bit PNG, got %T", decoded)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
tc.assertPx(t, decoded)
})
}
}

func TestFromBytes_Leaves8BitPNGUnchanged(t *testing.T) {
t.Parallel()

m := image.NewNRGBA(image.Rect(0, 0, 2, 2))
m.SetNRGBA(0, 0, color.NRGBA{R: 255, G: 128, B: 64, A: 255})

var input bytes.Buffer
require.NoError(t, png.Encode(&input, m))
original := append([]byte(nil), input.Bytes()...)

img, err := gofpdf.FromBytes(input.Bytes(), extension.Png)
require.NoError(t, err)
assert.Equal(t, original, img.Bytes)
}