mirror of
https://github.com/unidoc/unipdf.git
synced 2025-05-01 22:17:29 +08:00
V3: Detect and fix stride offsets in loaded go images (#448)
* Detect and fix stride offsets in loaded go images * Benchmarks for image copying performance * Account for potential image offsets when constructing model Image data Removes need for extra copying of image data
This commit is contained in:
parent
359d6965de
commit
7b7eccd65e
@ -164,7 +164,6 @@ func (img *Image) ToGoImage() (goimage.Image, error) {
|
|||||||
aidx := 0
|
aidx := 0
|
||||||
|
|
||||||
samples := img.GetSamples()
|
samples := img.GetSamples()
|
||||||
//bytesPerColor := colorComponents * int(img.BitsPerComponent) / 8
|
|
||||||
bytesPerColor := img.ColorComponents
|
bytesPerColor := img.ColorComponents
|
||||||
for i := 0; i+bytesPerColor-1 < len(samples); i += bytesPerColor {
|
for i := 0; i+bytesPerColor-1 < len(samples); i += bytesPerColor {
|
||||||
var c gocolor.Color
|
var c gocolor.Color
|
||||||
@ -253,22 +252,32 @@ func (ih DefaultImageHandler) NewImageFromGoImage(goimg goimage.Image) (*Image,
|
|||||||
// Will not be required once the golang image/jpeg package is optimized.
|
// Will not be required once the golang image/jpeg package is optimized.
|
||||||
m = goimage.NewRGBA(goimage.Rect(0, 0, b.Dx(), b.Dy()))
|
m = goimage.NewRGBA(goimage.Rect(0, 0, b.Dx(), b.Dy()))
|
||||||
draw.Draw(m, m.Bounds(), goimg, b.Min, draw.Src)
|
draw.Draw(m, m.Bounds(), goimg, b.Min, draw.Src)
|
||||||
|
b = m.Bounds()
|
||||||
}
|
}
|
||||||
|
|
||||||
numPixels := len(m.Pix) / 4
|
numPixels := b.Dx() * b.Dy()
|
||||||
data := make([]byte, 3*numPixels)
|
data := make([]byte, 3*numPixels)
|
||||||
alphaData := make([]byte, numPixels)
|
alphaData := make([]byte, numPixels)
|
||||||
hasAlpha := false
|
hasAlpha := false
|
||||||
|
|
||||||
for i := 0; i < numPixels; i++ {
|
i0 := m.PixOffset(b.Min.X, b.Min.Y)
|
||||||
data[3*i], data[3*i+1], data[3*i+2] = m.Pix[4*i], m.Pix[4*i+1], m.Pix[4*i+2]
|
i1 := i0 + b.Dx()*4
|
||||||
|
|
||||||
alpha := m.Pix[4*i+3]
|
j := 0
|
||||||
|
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||||
|
for i := i0; i < i1; i += 4 {
|
||||||
|
data[3*j], data[3*j+1], data[3*j+2] = m.Pix[i], m.Pix[i+1], m.Pix[i+2]
|
||||||
|
alpha := m.Pix[i+3]
|
||||||
if alpha != 255 {
|
if alpha != 255 {
|
||||||
// If all alpha values are 255 (opaque), means that the alpha transparency channel is unnecessary.
|
// If all alpha values are 255 (opaque), means that the alpha transparency channel is unnecessary.
|
||||||
hasAlpha = true
|
hasAlpha = true
|
||||||
}
|
}
|
||||||
alphaData[i] = alpha
|
alphaData[j] = alpha
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
|
||||||
|
i0 += m.Stride
|
||||||
|
i1 += m.Stride
|
||||||
}
|
}
|
||||||
|
|
||||||
imag := Image{}
|
imag := Image{}
|
||||||
@ -276,7 +285,7 @@ func (ih DefaultImageHandler) NewImageFromGoImage(goimg goimage.Image) (*Image,
|
|||||||
imag.Height = int64(b.Dy())
|
imag.Height = int64(b.Dy())
|
||||||
imag.BitsPerComponent = 8 // RGBA colormap
|
imag.BitsPerComponent = 8 // RGBA colormap
|
||||||
imag.ColorComponents = 3
|
imag.ColorComponents = 3
|
||||||
imag.Data = data // buf.Bytes()
|
imag.Data = data
|
||||||
|
|
||||||
imag.hasAlpha = hasAlpha
|
imag.hasAlpha = hasAlpha
|
||||||
if hasAlpha {
|
if hasAlpha {
|
||||||
@ -294,6 +303,15 @@ func (ih DefaultImageHandler) NewGrayImageFromGoImage(goimg goimage.Image) (*Ima
|
|||||||
switch t := goimg.(type) {
|
switch t := goimg.(type) {
|
||||||
case *goimage.Gray:
|
case *goimage.Gray:
|
||||||
m = t
|
m = t
|
||||||
|
if len(m.Pix) != b.Dx()*b.Dy() {
|
||||||
|
// Detects when the image Pix data is not of correct format, typically happens
|
||||||
|
// when m.Stride does not match the image width (extra bytes at end of each line for example).
|
||||||
|
// Rearrange the data back such that the Pix data is arranged consistently.
|
||||||
|
// Disadvantage of this is that it doubles the memory use as the data is
|
||||||
|
// copied when creating the new structure.
|
||||||
|
m = goimage.NewGray(b)
|
||||||
|
draw.Draw(m, b, goimg, b.Min, draw.Src)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
m = goimage.NewGray(b)
|
m = goimage.NewGray(b)
|
||||||
draw.Draw(m, b, goimg, b.Min, draw.Src)
|
draw.Draw(m, b, goimg, b.Min, draw.Src)
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -64,3 +67,78 @@ func TestImageResampling(t *testing.T) {
|
|||||||
t.Errorf("Value != 64 (%d)", img.Data[1])
|
t.Errorf("Value != 64 (%d)", img.Data[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeTestImage(x, y int, val byte) *image.RGBA {
|
||||||
|
rect := image.Rect(0, 0, x, y)
|
||||||
|
m := image.NewRGBA(rect)
|
||||||
|
|
||||||
|
for i := 0; i < x; i++ {
|
||||||
|
for j := 0; j < y; j++ {
|
||||||
|
rgba := color.RGBA{R: val, G: val, B: val, A: 0}
|
||||||
|
m.Set(i, j, rgba)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkImageCopyingDraw(b *testing.B) {
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
img := makeTestImage(100, 100, byte(n))
|
||||||
|
b := img.Bounds()
|
||||||
|
m := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
|
||||||
|
|
||||||
|
// Image copying using draw.Draw.
|
||||||
|
draw.Draw(m, img.Bounds(), img, b.Min, draw.Src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkImageCopyingAtSet(b *testing.B) {
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
img := makeTestImage(100, 100, byte(n))
|
||||||
|
b := img.Bounds()
|
||||||
|
m := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
|
||||||
|
|
||||||
|
// Image copying with At and direct set.
|
||||||
|
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
|
||||||
|
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
|
||||||
|
m.Set(x, y, img.At(x, y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkImageCopyingAtDirectSet(b *testing.B) {
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
img := makeTestImage(100, 100, byte(n))
|
||||||
|
b := img.Bounds()
|
||||||
|
m := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
|
||||||
|
|
||||||
|
// Image copying with At to get colors and set Pix directly.
|
||||||
|
i := 0
|
||||||
|
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
|
||||||
|
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
|
||||||
|
r, g, b, a := img.At(x, y).RGBA()
|
||||||
|
|
||||||
|
m.Pix[4*i], m.Pix[4*i+1], m.Pix[4*i+2], m.Pix[4*i+3] = byte(r), byte(g), byte(b), byte(a)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkImageCopyingDirect(b *testing.B) {
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
img := makeTestImage(100, 100, byte(n))
|
||||||
|
b := img.Bounds()
|
||||||
|
m := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
|
||||||
|
|
||||||
|
// Image copying with direct Pix access for getting and setting.
|
||||||
|
i := 0
|
||||||
|
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
|
||||||
|
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
|
||||||
|
m.Pix[4*i], m.Pix[4*i+1], m.Pix[4*i+2], m.Pix[4*i+3] = img.Pix[4*i], img.Pix[4*i+1], img.Pix[4*i+2], img.Pix[4*i+3]
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user