diff --git a/pdf/core/encoding.go b/pdf/core/encoding.go index 89fe27aa..9424b993 100644 --- a/pdf/core/encoding.go +++ b/pdf/core/encoding.go @@ -57,6 +57,7 @@ type StreamEncoder interface { GetFilterName() string MakeDecodeParams() PdfObject MakeStreamDict() *PdfObjectDictionary + UpdateParams(params *PdfObjectDictionary) EncodeBytes(data []byte) ([]byte, error) DecodeBytes(encoded []byte) ([]byte, error) @@ -136,6 +137,29 @@ func (enc *FlateEncoder) MakeStreamDict() *PdfObjectDictionary { return dict } +// UpdateParams updates the parameter values of the encoder. +func (enc *FlateEncoder) UpdateParams(params *PdfObjectDictionary) { + predictor, err := GetNumberAsInt64(params.Get("Predictor")) + if err == nil { + enc.Predictor = int(predictor) + } + + bpc, err := GetNumberAsInt64(params.Get("BitsPerComponent")) + if err == nil { + enc.BitsPerComponent = int(bpc) + } + + columns, err := GetNumberAsInt64(params.Get("Width")) + if err == nil { + enc.Columns = int(columns) + } + + colorComponents, err := GetNumberAsInt64(params.Get("ColorComponents")) + if err == nil { + enc.Colors = int(colorComponents) + } +} + // Create a new flate decoder from a stream object, getting all the encoding parameters // from the DecodeParms stream object dictionary entry. func newFlateEncoderFromStream(streamObj *PdfObjectStream, decodeParams *PdfObjectDictionary) (*FlateEncoder, error) { @@ -507,6 +531,34 @@ func (enc *LZWEncoder) MakeStreamDict() *PdfObjectDictionary { return dict } +// UpdateParams updates the parameter values of the encoder. +func (enc *LZWEncoder) UpdateParams(params *PdfObjectDictionary) { + predictor, err := GetNumberAsInt64(params.Get("Predictor")) + if err == nil { + enc.Predictor = int(predictor) + } + + bpc, err := GetNumberAsInt64(params.Get("BitsPerComponent")) + if err == nil { + enc.BitsPerComponent = int(bpc) + } + + columns, err := GetNumberAsInt64(params.Get("Width")) + if err == nil { + enc.Columns = int(columns) + } + + colorComponents, err := GetNumberAsInt64(params.Get("ColorComponents")) + if err == nil { + enc.Colors = int(colorComponents) + } + + earlyChange, err := GetNumberAsInt64(params.Get("EarlyChange")) + if err == nil { + enc.EarlyChange = int(earlyChange) + } +} + // Create a new LZW encoder/decoder from a stream object, getting all the encoding parameters // from the DecodeParms stream object dictionary entry. func newLZWEncoderFromStream(streamObj *PdfObjectStream, decodeParams *PdfObjectDictionary) (*LZWEncoder, error) { @@ -824,6 +876,34 @@ func (enc *DCTEncoder) MakeStreamDict() *PdfObjectDictionary { return dict } +// UpdateParams updates the parameter values of the encoder. +func (enc *DCTEncoder) UpdateParams(params *PdfObjectDictionary) { + colorComponents, err := GetNumberAsInt64(params.Get("ColorComponents")) + if err == nil { + enc.ColorComponents = int(colorComponents) + } + + bpc, err := GetNumberAsInt64(params.Get("BitsPerComponent")) + if err == nil { + enc.BitsPerComponent = int(bpc) + } + + width, err := GetNumberAsInt64(params.Get("Width")) + if err == nil { + enc.Width = int(width) + } + + height, err := GetNumberAsInt64(params.Get("Height")) + if err == nil { + enc.Height = int(height) + } + + quality, err := GetNumberAsInt64(params.Get("Quality")) + if err == nil { + enc.Quality = int(quality) + } +} + // Create a new DCT encoder/decoder from a stream object, getting all the encoding parameters // from the stream object dictionary entry and the image data itself. // TODO: Support if used with other filters [ASCII85Decode FlateDecode DCTDecode]... @@ -1037,9 +1117,17 @@ func (enc *DCTEncoder) EncodeBytes(data []byte) ([]byte, error) { } // Draw the data on the image.. + if enc.BitsPerComponent < 8 { + enc.BitsPerComponent = 8 + } + + bytesPerColor := enc.ColorComponents * enc.BitsPerComponent / 8 + if bytesPerColor < 1 { + bytesPerColor = 1 + } + x := 0 y := 0 - bytesPerColor := enc.ColorComponents * enc.BitsPerComponent / 8 for i := 0; i+bytesPerColor-1 < len(data); i += bytesPerColor { var c gocolor.Color if enc.ColorComponents == 1 { @@ -1238,6 +1326,10 @@ func (enc *RunLengthEncoder) MakeStreamDict() *PdfObjectDictionary { return dict } +// UpdateParams updates the parameter values of the encoder. +func (enc *RunLengthEncoder) UpdateParams(params *PdfObjectDictionary) { +} + // ASCIIHexEncoder implements ASCII hex encoder/decoder. type ASCIIHexEncoder struct { } @@ -1263,6 +1355,10 @@ func (enc *ASCIIHexEncoder) MakeStreamDict() *PdfObjectDictionary { return dict } +// UpdateParams updates the parameter values of the encoder. +func (enc *ASCIIHexEncoder) UpdateParams(params *PdfObjectDictionary) { +} + func (enc *ASCIIHexEncoder) DecodeBytes(encoded []byte) ([]byte, error) { bufReader := bytes.NewReader(encoded) var inb []byte @@ -1337,6 +1433,10 @@ func (enc *ASCII85Encoder) MakeStreamDict() *PdfObjectDictionary { return dict } +// UpdateParams updates the parameter values of the encoder. +func (enc *ASCII85Encoder) UpdateParams(params *PdfObjectDictionary) { +} + // DecodeBytes decodes byte array with ASCII85. 5 ASCII characters -> 4 raw binary bytes func (enc *ASCII85Encoder) DecodeBytes(encoded []byte) ([]byte, error) { var decoded []byte @@ -1502,6 +1602,10 @@ func (enc *RawEncoder) MakeStreamDict() *PdfObjectDictionary { return MakeDict() } +// UpdateParams updates the parameter values of the encoder. +func (enc *RawEncoder) UpdateParams(params *PdfObjectDictionary) { +} + func (enc *RawEncoder) DecodeBytes(encoded []byte) ([]byte, error) { return encoded, nil } @@ -1535,6 +1639,10 @@ func (enc *CCITTFaxEncoder) MakeStreamDict() *PdfObjectDictionary { return MakeDict() } +// UpdateParams updates the parameter values of the encoder. +func (enc *CCITTFaxEncoder) UpdateParams(params *PdfObjectDictionary) { +} + func (enc *CCITTFaxEncoder) DecodeBytes(encoded []byte) ([]byte, error) { common.Log.Debug("Error: Attempting to use unsupported encoding %s", enc.GetFilterName()) return encoded, ErrNoCCITTFaxDecode @@ -1571,6 +1679,10 @@ func (enc *JBIG2Encoder) MakeStreamDict() *PdfObjectDictionary { return MakeDict() } +// UpdateParams updates the parameter values of the encoder. +func (enc *JBIG2Encoder) UpdateParams(params *PdfObjectDictionary) { +} + func (enc *JBIG2Encoder) DecodeBytes(encoded []byte) ([]byte, error) { common.Log.Debug("Error: Attempting to use unsupported encoding %s", enc.GetFilterName()) return encoded, ErrNoJBIG2Decode @@ -1607,6 +1719,10 @@ func (enc *JPXEncoder) MakeStreamDict() *PdfObjectDictionary { return MakeDict() } +// UpdateParams updates the parameter values of the encoder. +func (enc *JPXEncoder) UpdateParams(params *PdfObjectDictionary) { +} + func (enc *JPXEncoder) DecodeBytes(encoded []byte) ([]byte, error) { common.Log.Debug("Error: Attempting to use unsupported encoding %s", enc.GetFilterName()) return encoded, ErrNoJPXDecode @@ -1805,6 +1921,13 @@ func (enc *MultiEncoder) MakeStreamDict() *PdfObjectDictionary { return dict } +// UpdateParams updates the parameter values of the encoder. +func (enc *MultiEncoder) UpdateParams(params *PdfObjectDictionary) { + for _, encoder := range enc.encoders { + encoder.UpdateParams(params) + } +} + func (enc *MultiEncoder) DecodeBytes(encoded []byte) ([]byte, error) { decoded := encoded var err error diff --git a/pdf/model/colorspace.go b/pdf/model/colorspace.go index a82006fe..2545fcfd 100644 --- a/pdf/model/colorspace.go +++ b/pdf/model/colorspace.go @@ -300,7 +300,7 @@ func (cs *PdfColorspaceDeviceGray) ImageToRGB(img Image) (Image, error) { var rgbSamples []uint32 for i := 0; i < len(samples); i++ { - grayVal := samples[i] + grayVal := samples[i] * 255 / uint32(math.Pow(2, float64(img.BitsPerComponent))-1) rgbSamples = append(rgbSamples, grayVal, grayVal, grayVal) } rgbImage.BitsPerComponent = 8 diff --git a/pdf/model/image.go b/pdf/model/image.go index 1577dc88..f45402c4 100644 --- a/pdf/model/image.go +++ b/pdf/model/image.go @@ -13,6 +13,7 @@ import ( _ "image/gif" _ "image/png" "io" + "math" "github.com/unidoc/unidoc/common" . "github.com/unidoc/unidoc/pdf/core" @@ -170,8 +171,8 @@ func (img *Image) ToGoImage() (goimage.Image, error) { val := uint16(samples[i])<<8 | uint16(samples[i+1]) c = gocolor.Gray16{val} } else { - val := uint8(samples[i] & 0xff) - c = gocolor.Gray{val} + val := samples[i] * 255 / uint32(math.Pow(2, float64(img.BitsPerComponent))-1) + c = gocolor.Gray{uint8(val & 0xff)} } } else if img.ColorComponents == 3 { if img.BitsPerComponent == 16 { @@ -221,9 +222,12 @@ type ImageHandler interface { // Read any image type and load into a new Image object. Read(r io.Reader) (*Image, error) - // NewImageFromGoImage load a unidoc Image from a standard Go image structure. + // NewImageFromGoImage loads a RGBA unidoc Image from a standard Go image structure. NewImageFromGoImage(goimg goimage.Image) (*Image, error) + // NewGrayImageFromGoImage loads a grayscale unidoc Image from a standard Go image structure. + NewGrayImageFromGoImage(goimg goimage.Image) (*Image, error) + // Compress an image. Compress(input *Image, quality int64) (*Image, error) } @@ -231,7 +235,7 @@ type ImageHandler interface { // DefaultImageHandler is the default implementation of the ImageHandler using the standard go library. type DefaultImageHandler struct{} -// NewImageFromGoImage creates a unidoc Image from a golang Image. +// NewImageFromGoImage creates a new RGBA unidoc Image from a golang Image. func (ih DefaultImageHandler) NewImageFromGoImage(goimg goimage.Image) (*Image, error) { // Speed up jpeg encoding by converting to RGBA first. // Will not be required once the golang image/jpeg package is optimized. @@ -269,6 +273,21 @@ func (ih DefaultImageHandler) NewImageFromGoImage(goimg goimage.Image) (*Image, return &imag, nil } +// NewGrayImageFromGoImage creates a new grayscale unidoc Image from a golang Image. +func (ih DefaultImageHandler) NewGrayImageFromGoImage(goimg goimage.Image) (*Image, error) { + b := goimg.Bounds() + m := goimage.NewGray(b) + draw.Draw(m, b, goimg, b.Min, draw.Src) + + return &Image{ + Width: int64(b.Dx()), + Height: int64(b.Dy()), + BitsPerComponent: 8, + ColorComponents: 1, + Data: m.Pix, + }, nil +} + // Read reads an image and loads into a new Image object with an RGB // colormap and 8 bits per component. func (ih DefaultImageHandler) Read(reader io.Reader) (*Image, error) { diff --git a/pdf/model/optimize/image_ppi.go b/pdf/model/optimize/image_ppi.go index f337624f..362cf62e 100644 --- a/pdf/model/optimize/image_ppi.go +++ b/pdf/model/optimize/image_ppi.go @@ -41,21 +41,41 @@ func scaleImage(stream *core.PdfObjectStream, scale float64) error { newW := int(math.RoundToEven(float64(i.Width) * scale)) newH := int(math.RoundToEven(float64(i.Height) * scale)) rect := image.Rect(0, 0, newW, newH) + var newImage draw.Image + var imageHandler func(image.Image) (*model.Image, error) + switch xImg.ColorSpace.String() { case "DeviceRGB": newImage = image.NewRGBA(rect) + imageHandler = model.ImageHandling.NewImageFromGoImage case "DeviceGray": newImage = image.NewGray(rect) + imageHandler = model.ImageHandling.NewGrayImageFromGoImage default: return fmt.Errorf("optimization is not supported for color space %s", xImg.ColorSpace.String()) } + draw.CatmullRom.Scale(newImage, newImage.Bounds(), goimg, goimg.Bounds(), draw.Over, &draw.Options{}) - i, err = model.ImageHandling.NewImageFromGoImage(newImage) - if err != nil { + if i, err = imageHandler(newImage); err != nil { + return err + } + + // Update image encoder + encoderParams := core.MakeDict() + encoderParams.Set("ColorComponents", core.MakeInteger(int64(i.ColorComponents))) + encoderParams.Set("BitsPerComponent", core.MakeInteger(i.BitsPerComponent)) + encoderParams.Set("Width", core.MakeInteger(i.Width)) + encoderParams.Set("Height", core.MakeInteger(i.Height)) + encoderParams.Set("Quality", core.MakeInteger(100)) + encoderParams.Set("Predictor", core.MakeInteger(1)) + + xImg.Filter.UpdateParams(encoderParams) + + // Update image + if err := xImg.SetImage(i, nil); err != nil { return err } - xImg.SetImage(i, xImg.ColorSpace) xImg.ToPdfObject() return nil } diff --git a/pdf/model/xobject.go b/pdf/model/xobject.go index 8b054f53..96d650b2 100644 --- a/pdf/model/xobject.go +++ b/pdf/model/xobject.go @@ -455,9 +455,14 @@ func (ximg *XObjectImage) SetImage(img *Image, cs PdfColorspace) error { ximg.Stream = encoded // Width, height and bits. - ximg.Width = &img.Width - ximg.Height = &img.Height - ximg.BitsPerComponent = &img.BitsPerComponent + w := img.Width + ximg.Width = &w + + h := img.Height + ximg.Height = &h + + bpc := img.BitsPerComponent + ximg.BitsPerComponent = &bpc // Guess colorspace if not explicitly set. if cs == nil {