mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-24 13:48:49 +08:00
JBIG2 Encoder support for inserting binary images into PDF (#288)
* Added JBIG2 PDF support * Added JBIG2 Encoder binary image requirements * PR #288 revision r1 fixes * PR #288 revision r2 fixes
This commit is contained in:
parent
64a43b38d2
commit
29efa30439
@ -37,6 +37,7 @@ func NewInlineImageFromImage(img model.Image, encoder core.StreamEncoder) (*Cont
|
||||
if encoder == nil {
|
||||
encoder = core.NewRawEncoder()
|
||||
}
|
||||
encoder.UpdateParams(img.GetParamsDict())
|
||||
|
||||
inlineImage := ContentStreamInlineImage{}
|
||||
if img.ColorComponents == 1 {
|
||||
|
@ -6,11 +6,11 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"github.com/unidoc/unipdf/v3/common"
|
||||
"github.com/unidoc/unipdf/v3/internal/imageutil"
|
||||
|
||||
"github.com/unidoc/unipdf/v3/internal/jbig2"
|
||||
"github.com/unidoc/unipdf/v3/internal/jbig2/bitmap"
|
||||
@ -55,6 +55,17 @@ const JB2ImageAutoThreshold = -1.0
|
||||
// The similarity is defined by the 'Threshold' variable (default: 0.95). The less the value is, the more components
|
||||
// matches to single class, thus the compression is better, but the result might become lossy.
|
||||
type JBIG2Encoder struct {
|
||||
// These values are required to be set for the 'EncodeBytes' method.
|
||||
// ColorComponents defines the number of color components for provided image.
|
||||
ColorComponents int
|
||||
// BitsPerComponent is the number of bits that stores per color component
|
||||
BitsPerComponent int
|
||||
// Width is the width of the image to encode
|
||||
Width int
|
||||
// Height is the height of the image to encode.
|
||||
Height int
|
||||
|
||||
// Encode Page and Decode parameters
|
||||
d *document.Document
|
||||
// Globals are the JBIG2 global segments.
|
||||
Globals jbig2.Globals
|
||||
@ -69,7 +80,9 @@ type JBIG2Encoder struct {
|
||||
|
||||
// NewJBIG2Encoder creates a new JBIG2Encoder.
|
||||
func NewJBIG2Encoder() *JBIG2Encoder {
|
||||
return &JBIG2Encoder{}
|
||||
return &JBIG2Encoder{
|
||||
d: document.InitEncodeDocument(false),
|
||||
}
|
||||
}
|
||||
|
||||
// AddPageImage adds the page with the image 'img' to the encoder context in order to encode it jbig2 document.
|
||||
@ -165,18 +178,31 @@ func (enc *JBIG2Encoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, error
|
||||
// to encode given image.
|
||||
func (enc *JBIG2Encoder) EncodeBytes(data []byte) ([]byte, error) {
|
||||
const processName = "JBIG2Encoder.EncodeBytes"
|
||||
if len(data) == 0 {
|
||||
return nil, errors.Errorf(processName, "input 'data' not defined")
|
||||
if enc.ColorComponents != 1 || enc.BitsPerComponent != 1 {
|
||||
return nil, errors.Errorf(processName, "provided invalid input image. JBIG2 Encoder requires binary images data")
|
||||
}
|
||||
i, _, err := image.Decode(bytes.NewReader(data))
|
||||
b, err := bitmap.NewWithUnpaddedData(enc.Width, enc.Height, data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, processName, "decode input image")
|
||||
return nil, err
|
||||
}
|
||||
encoded, err := enc.encodeImage(i)
|
||||
if err != nil {
|
||||
settings := enc.DefaultPageSettings
|
||||
if err = settings.Validate(); err != nil {
|
||||
return nil, errors.Wrap(err, processName, "")
|
||||
}
|
||||
return encoded, nil
|
||||
|
||||
switch settings.Compression {
|
||||
case JB2Generic:
|
||||
if err = enc.d.AddGenericPage(b, settings.DuplicatedLinesRemoval); err != nil {
|
||||
return nil, errors.Wrap(err, processName, "")
|
||||
}
|
||||
case JB2SymbolCorrelation:
|
||||
return nil, errors.Error(processName, "symbol correlation encoding not implemented yet")
|
||||
case JB2SymbolRankHaus:
|
||||
return nil, errors.Error(processName, "symbol rank haus encoding not implemented yet")
|
||||
default:
|
||||
return nil, errors.Error(processName, "provided invalid compression")
|
||||
}
|
||||
return enc.Encode()
|
||||
}
|
||||
|
||||
// EncodeImage encodes 'img' golang image.Image into jbig2 encoded bytes document using default encoder settings.
|
||||
@ -184,6 +210,15 @@ func (enc *JBIG2Encoder) EncodeImage(img image.Image) ([]byte, error) {
|
||||
return enc.encodeImage(img)
|
||||
}
|
||||
|
||||
// EncodeJBIG2Image encodes 'img' into jbig2 encoded bytes stream, using default encoder settings.
|
||||
func (enc *JBIG2Encoder) EncodeJBIG2Image(img *JBIG2Image) ([]byte, error) {
|
||||
const processName = "core.EncodeJBIG2Image"
|
||||
if err := enc.AddPageImage(img, &enc.DefaultPageSettings); err != nil {
|
||||
return nil, errors.Wrap(err, processName, "")
|
||||
}
|
||||
return enc.Encode()
|
||||
}
|
||||
|
||||
// Encode encodes previously prepare jbig2 document and stores it as the byte slice.
|
||||
func (enc *JBIG2Encoder) Encode() (data []byte, err error) {
|
||||
const processName = "JBIG2Document.Encode"
|
||||
@ -217,8 +252,24 @@ func (enc *JBIG2Encoder) MakeStreamDict() *PdfObjectDictionary {
|
||||
}
|
||||
|
||||
// UpdateParams updates the parameter values of the encoder.
|
||||
// The body of this method is empty but required to implement StreamEncoder interface.
|
||||
// Implements StreamEncoder interface.
|
||||
func (enc *JBIG2Encoder) UpdateParams(params *PdfObjectDictionary) {
|
||||
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)
|
||||
}
|
||||
colorComponents, err := GetNumberAsInt64(params.Get("ColorComponents"))
|
||||
if err == nil {
|
||||
enc.ColorComponents = int(colorComponents)
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *JBIG2Encoder) encodeImage(i image.Image) ([]byte, error) {
|
||||
@ -262,24 +313,29 @@ func newJBIG2DecoderFromStream(streamObj *PdfObjectStream, decodeParams *PdfObje
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if decodeParams != nil {
|
||||
if globals := decodeParams.Get("JBIG2Globals"); globals != nil {
|
||||
var err error
|
||||
|
||||
globalsStream, ok := globals.(*PdfObjectStream)
|
||||
if !ok {
|
||||
err = errors.Error(processName, "jbig2.Globals stream should be an Object Stream")
|
||||
common.Log.Debug("ERROR: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
encoder.Globals, err = jbig2.DecodeGlobals(globalsStream.Stream)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, processName, "corrupted jbig2 encoded data")
|
||||
common.Log.Debug("ERROR: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// if no decode params provided - end fast.
|
||||
if decodeParams == nil {
|
||||
return encoder, nil
|
||||
}
|
||||
// set image parameters.
|
||||
encoder.UpdateParams(decodeParams)
|
||||
globals := decodeParams.Get("JBIG2Globals")
|
||||
if globals == nil {
|
||||
return encoder, nil
|
||||
}
|
||||
// decode and set JBIG2 Globals.
|
||||
var err error
|
||||
globalsStream, ok := globals.(*PdfObjectStream)
|
||||
if !ok {
|
||||
err = errors.Error(processName, "jbig2.Globals stream should be an Object Stream")
|
||||
common.Log.Debug("ERROR: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
encoder.Globals, err = jbig2.DecodeGlobals(globalsStream.Stream)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, processName, "corrupted jbig2 encoded data")
|
||||
common.Log.Debug("ERROR: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return encoder, nil
|
||||
}
|
||||
@ -348,9 +404,9 @@ func GoImageToJBIG2(i image.Image, bwThreshold float64) (*JBIG2Image, error) {
|
||||
var th uint8
|
||||
if bwThreshold == JB2ImageAutoThreshold {
|
||||
// autoThreshold using triangle method
|
||||
gray := bitmap.ImgToGray(i)
|
||||
histogram := bitmap.GrayImageHistogram(gray)
|
||||
th = bitmap.AutoThresholdTriangle(histogram)
|
||||
gray := imageutil.ImgToGray(i)
|
||||
histogram := imageutil.GrayImageHistogram(gray)
|
||||
th = imageutil.AutoThresholdTriangle(histogram)
|
||||
i = gray
|
||||
} else if bwThreshold > 1.0 || bwThreshold < 0.0 {
|
||||
// check if bwThreshold is unknown - set to 0.0 is not in the allowed range.
|
||||
@ -358,7 +414,7 @@ func GoImageToJBIG2(i image.Image, bwThreshold float64) (*JBIG2Image, error) {
|
||||
} else {
|
||||
th = uint8(255 * bwThreshold)
|
||||
}
|
||||
gray := bitmap.ImgToBinary(i, th)
|
||||
gray := imageutil.ImgToBinary(i, th)
|
||||
return bwToJBIG2Image(gray), nil
|
||||
}
|
||||
|
||||
|
@ -158,6 +158,13 @@ func (img *Image) GetMargins() (float64, float64, float64, float64) {
|
||||
return img.margins.left, img.margins.right, img.margins.top, img.margins.bottom
|
||||
}
|
||||
|
||||
// ConvertToBinary converts current image data into binary (Bi-level image) format.
|
||||
// If provided image is RGB or GrayScale the function converts it into binary image
|
||||
// using histogram auto threshold method.
|
||||
func (img *Image) ConvertToBinary() error {
|
||||
return img.img.ConvertToBinary()
|
||||
}
|
||||
|
||||
// makeXObject makes the encoded XObject Image that will be used in the PDF.
|
||||
func (img *Image) makeXObject() error {
|
||||
encoder := img.encoder
|
||||
@ -181,7 +188,10 @@ func (img *Image) makeXObject() error {
|
||||
func (img *Image) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
|
||||
if img.xobj == nil {
|
||||
// Build the XObject Image if not already prepared.
|
||||
img.makeXObject()
|
||||
if err := img.makeXObject(); err != nil {
|
||||
return nil, ctx, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var blocks []*Block
|
||||
|
1
go.sum
1
go.sum
@ -8,6 +8,7 @@ github.com/boombuler/barcode v1.0.0 h1:s1TvRnXwL2xJRaccrdcBQMZxq6X7DvsMogtmJeHDd
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/gunnsth/pkcs7 v0.0.0-20181213175627-3cffc6fbfe83 h1:saj5dTV7eQ1wFg/gVZr1SfbkOmg8CYO9R8frHgQiyR4=
|
||||
github.com/gunnsth/pkcs7 v0.0.0-20181213175627-3cffc6fbfe83/go.mod h1:xaGEIRenAiJcGgd9p62zbiP4993KaV3PdjczwGnP50I=
|
||||
|
@ -1,4 +1,4 @@
|
||||
package bitmap
|
||||
package imageutil
|
||||
|
||||
import (
|
||||
"image"
|
||||
@ -131,6 +131,11 @@ func ImgToGray(i image.Image) *image.Gray {
|
||||
return g
|
||||
}
|
||||
|
||||
// IsGrayImgBlackAndWhite checks if provided gray image is BlackAndWhite - Binary image.
|
||||
func IsGrayImgBlackAndWhite(i *image.Gray) bool {
|
||||
return isGrayBlackWhite(i)
|
||||
}
|
||||
|
||||
func blackOrWhite(c, threshold uint8) uint8 {
|
||||
if c < threshold {
|
||||
return 255
|
@ -545,7 +545,7 @@ func (b *Bitmap) addBorderGeneral(left, right, top, bot int, val int) (*Bitmap,
|
||||
|
||||
// addPadBits creates new data byte slice that contains extra padding on the last byte for each row.
|
||||
func (b *Bitmap) addPadBits() (err error) {
|
||||
const processName = "addPadBits"
|
||||
const processName = "bitmap.addPadBits"
|
||||
endbits := b.Width % 8
|
||||
if endbits == 0 {
|
||||
// no partial words
|
||||
@ -559,18 +559,16 @@ func (b *Bitmap) addPadBits() (err error) {
|
||||
w := writer.NewMSB(data)
|
||||
temp := make([]byte, fullBytes)
|
||||
var (
|
||||
i, j int
|
||||
i int
|
||||
bits uint64
|
||||
)
|
||||
for i = 0; i < b.Height; i++ {
|
||||
// iterate over full bytes
|
||||
for j = 0; j < fullBytes; j++ {
|
||||
if _, err = r.Read(temp); err != nil {
|
||||
return errors.Wrap(err, processName, "full byte")
|
||||
}
|
||||
if _, err = w.Write(temp); err != nil {
|
||||
return errors.Wrap(err, processName, "full bytes")
|
||||
}
|
||||
if _, err = r.Read(temp); err != nil {
|
||||
return errors.Wrap(err, processName, "full byte")
|
||||
}
|
||||
if _, err = w.Write(temp); err != nil {
|
||||
return errors.Wrap(err, processName, "full bytes")
|
||||
}
|
||||
// read unused bits
|
||||
if bits, err = r.ReadBits(byte(endbits)); err != nil {
|
||||
|
@ -98,7 +98,7 @@ func (d *Document) AddGenericPage(bm *bitmap.Bitmap, duplicateLineRemoval bool)
|
||||
const processName = "Document.AddGenericPage"
|
||||
// check if this is PDFMode and there is already a page
|
||||
if !d.FullHeaders && d.NumberOfPages != 0 {
|
||||
return errors.Error(processName, "document already contains page. FileMode disallows addoing more than one page")
|
||||
return errors.Error(processName, "document already contains page. FileMode disallows adding more than one page")
|
||||
}
|
||||
// initialize page
|
||||
page := &Page{
|
||||
|
1
internal/jbig2/tests/.gitignore
vendored
1
internal/jbig2/tests/.gitignore
vendored
@ -3,3 +3,4 @@ jbig2files
|
||||
.test
|
||||
*.jbig2
|
||||
.envrc
|
||||
.env
|
||||
|
155
internal/jbig2/tests/encode_pdf_test.go
Normal file
155
internal/jbig2/tests/encode_pdf_test.go
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/unidoc/unipdf/v3/common"
|
||||
"github.com/unidoc/unipdf/v3/core"
|
||||
"github.com/unidoc/unipdf/v3/creator"
|
||||
)
|
||||
|
||||
// TestImageEncodeJBIG2PDF tests the encode process for the JBIG2 encoder into PDF file.
|
||||
func TestImageEncodeJBIG2PDF(t *testing.T) {
|
||||
dirName := os.Getenv(EnvImageDirectory)
|
||||
if dirName == "" {
|
||||
t.Skipf("no environment variable: '%s' provided", EnvImageDirectory)
|
||||
}
|
||||
|
||||
// get the file names within given directory
|
||||
fileNames, err := readFileNames(dirName, "jpg")
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(fileNames) == 0 {
|
||||
t.Skipf("no files found in the '%s' directory", dirName)
|
||||
}
|
||||
|
||||
// prepare temporary directory where the jbig2 files would be stored
|
||||
tempDir := filepath.Join(os.TempDir(), "unipdf", "jbig2", "encoded-pdf")
|
||||
err = os.MkdirAll(tempDir, 0700)
|
||||
require.NoError(t, err)
|
||||
|
||||
var f *os.File
|
||||
switch {
|
||||
case logToFile:
|
||||
fileName := filepath.Join(tempDir, fmt.Sprintf("log_%s.txt", time.Now().Format("20060102")))
|
||||
f, err = os.Create(fileName)
|
||||
require.NoError(t, err)
|
||||
common.SetLogger(common.NewWriterLogger(common.LogLevelTrace, f))
|
||||
case testing.Verbose():
|
||||
common.SetLogger(common.NewConsoleLogger(common.LogLevelDebug))
|
||||
}
|
||||
|
||||
// clear all the temporary files
|
||||
defer func() {
|
||||
if f != nil {
|
||||
f.Close()
|
||||
}
|
||||
|
||||
switch {
|
||||
case !keepEncodedFile && !logToFile:
|
||||
err = os.RemoveAll(filepath.Join(tempDir))
|
||||
case !keepEncodedFile:
|
||||
err = filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasSuffix(info.Name(), "zip") {
|
||||
return os.Remove(path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
common.Log.Error(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
if !keepEncodedFile {
|
||||
os.RemoveAll(tempDir)
|
||||
}
|
||||
}()
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
h := md5.New()
|
||||
edp := []goldenValuePair{}
|
||||
|
||||
for _, fileName := range fileNames {
|
||||
var duplicateLinesRemoval bool
|
||||
duplicateLinesName := "NoDuplicateLinesRemoval"
|
||||
for i := 0; i < 2; i++ {
|
||||
if i == 1 {
|
||||
duplicateLinesRemoval = true
|
||||
duplicateLinesName = duplicateLinesName[2:]
|
||||
}
|
||||
t.Run(rawFileName(fileName)+duplicateLinesName, func(t *testing.T) {
|
||||
// read the file
|
||||
c := creator.New()
|
||||
|
||||
img, err := c.NewImageFromFile(filepath.Join(dirName, fileName))
|
||||
require.NoError(t, err)
|
||||
|
||||
// conver an image to binary image
|
||||
err = img.ConvertToBinary()
|
||||
require.NoError(t, err)
|
||||
|
||||
img.ScaleToWidth(612.0)
|
||||
|
||||
e := core.NewJBIG2Encoder()
|
||||
if duplicateLinesRemoval {
|
||||
e.DefaultPageSettings.DuplicatedLinesRemoval = true
|
||||
}
|
||||
img.SetEncoder(e)
|
||||
|
||||
// Use page width of 612 points, and calculate the height proportionally based on the image.
|
||||
// Standard PPI is 72 points per inch, thus a width of 8.5"
|
||||
height := 612.0 * img.Height() / img.Width()
|
||||
c.NewPage()
|
||||
c.SetPageSize(creator.PageSize{612, height})
|
||||
// c.SetPageSize(creator.PageSize{img.Width() * 1.2, img.Height() * 1.2})
|
||||
img.SetPos(0, 0)
|
||||
|
||||
err = c.Draw(img)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = c.Write(buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = h.Write(buf.Bytes())
|
||||
require.NoError(t, err)
|
||||
|
||||
if keepEncodedFile {
|
||||
f, err := os.Create(filepath.Join(tempDir, rawFileName(fileName)+duplicateLinesName+".pdf"))
|
||||
require.NoError(t, err)
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Write(buf.Bytes())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
hashEncoded := h.Sum(nil)
|
||||
buf.Reset()
|
||||
|
||||
edp = append(edp, goldenValuePair{
|
||||
Filename: rawFileName(fileName) + duplicateLinesName,
|
||||
Hash: hashEncoded,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
const goldenFileName = "encoded-pdf"
|
||||
checkGoldenValuePairs(t, dirName, goldenFileName, edp...)
|
||||
}
|
@ -11,14 +11,15 @@ import (
|
||||
goimage "image"
|
||||
gocolor "image/color"
|
||||
"image/draw"
|
||||
"io"
|
||||
|
||||
// Imported for initialization side effects.
|
||||
_ "image/gif"
|
||||
_ "image/png"
|
||||
"io"
|
||||
|
||||
"github.com/unidoc/unipdf/v3/common"
|
||||
"github.com/unidoc/unipdf/v3/core"
|
||||
"github.com/unidoc/unipdf/v3/internal/imageutil"
|
||||
"github.com/unidoc/unipdf/v3/internal/jbig2/bitmap"
|
||||
"github.com/unidoc/unipdf/v3/internal/sampling"
|
||||
)
|
||||
|
||||
@ -50,6 +51,59 @@ func (img *Image) AlphaMap(mapFunc AlphaMapFunc) {
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertToBinary converts current image into binary (bi-level) format.
|
||||
// Binary images are composed of single bits per pixel (only black or white).
|
||||
// If provided image has more color components, then it would be converted into binary image using
|
||||
// histogram auto threshold function.
|
||||
func (img *Image) ConvertToBinary() error {
|
||||
// check if given image is already a binary image (1 bit per component - 1 color component - the size of the data
|
||||
// is equal to the multiplication of width and height.
|
||||
if img.ColorComponents == 1 && img.BitsPerComponent == 1 {
|
||||
return nil
|
||||
}
|
||||
i, err := img.ToGoImage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gray := imageutil.ImgToGray(i)
|
||||
// check if 'img' is already a binary image.
|
||||
if !imageutil.IsGrayImgBlackAndWhite(gray) {
|
||||
threshold := imageutil.AutoThresholdTriangle(imageutil.GrayImageHistogram(gray))
|
||||
gray = imageutil.ImgToBinary(i, threshold)
|
||||
}
|
||||
// use JBIG2 bitmap as the temporary binary data converter - by default it uses
|
||||
tmpBM := bitmap.New(int(img.Width), int(img.Height))
|
||||
for y := 0; y < tmpBM.Height; y++ {
|
||||
for x := 0; x < tmpBM.Width; x++ {
|
||||
c := gray.GrayAt(x, y)
|
||||
// set only the white pixel - c.Y != 0
|
||||
if c.Y != 0 {
|
||||
if err = tmpBM.SetPixel(x, y, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unpaddedData, err := tmpBM.GetUnpaddedData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
img.BitsPerComponent = 1
|
||||
img.ColorComponents = 1
|
||||
img.Data = unpaddedData
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetParamsDict returns *core.PdfObjectDictionary with a set of basic image parameters.
|
||||
func (img *Image) GetParamsDict() *core.PdfObjectDictionary {
|
||||
params := core.MakeDict()
|
||||
params.Set("Width", core.MakeInteger(img.Width))
|
||||
params.Set("Height", core.MakeInteger(img.Height))
|
||||
params.Set("ColorComponents", core.MakeInteger(int64(img.ColorComponents)))
|
||||
params.Set("BitsPerComponent", core.MakeInteger(img.BitsPerComponent))
|
||||
return params
|
||||
}
|
||||
|
||||
// GetSamples converts the raw byte slice into samples which are stored in a uint32 bit array.
|
||||
// Each sample is represented by BitsPerComponent consecutive bits in the raw data.
|
||||
// NOTE: The method resamples the image byte data before returning the result and
|
||||
@ -294,6 +348,15 @@ func (img *Image) Resample(targetBitsPerComponent int64) {
|
||||
img.BitsPerComponent = int64(targetBitsPerComponent)
|
||||
}
|
||||
|
||||
// ToJBIG2Image converts current image to the core.JBIG2Image.
|
||||
func (img *Image) ToJBIG2Image() (*core.JBIG2Image, error) {
|
||||
goImg, err := img.ToGoImage()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return core.GoImageToJBIG2(goImg, core.JB2ImageAutoThreshold)
|
||||
}
|
||||
|
||||
// ToGoImage converts the unidoc Image to a golang Image structure.
|
||||
func (img *Image) ToGoImage() (goimage.Image, error) {
|
||||
common.Log.Trace("Converting to go image")
|
||||
|
@ -240,20 +240,17 @@ func NewXObjectImageFromImage(img *Image, cs PdfColorspace, encoder core.StreamE
|
||||
// If `encoder` is nil, uses raw encoding (none).
|
||||
func UpdateXObjectImageFromImage(xobjIn *XObjectImage, img *Image, cs PdfColorspace,
|
||||
encoder core.StreamEncoder) (*XObjectImage, error) {
|
||||
xobj := NewXObjectImage()
|
||||
|
||||
if encoder == nil {
|
||||
encoder = core.NewRawEncoder()
|
||||
}
|
||||
encoder.UpdateParams(img.GetParamsDict())
|
||||
|
||||
encoded, err := encoder.EncodeBytes(img.Data)
|
||||
if err != nil {
|
||||
common.Log.Debug("Error with encoding: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
xobj.Filter = encoder
|
||||
xobj.Stream = encoded
|
||||
xobj := NewXObjectImage()
|
||||
|
||||
// Width and height.
|
||||
imWidth := img.Width
|
||||
@ -261,8 +258,12 @@ func UpdateXObjectImageFromImage(xobjIn *XObjectImage, img *Image, cs PdfColorsp
|
||||
xobj.Width = &imWidth
|
||||
xobj.Height = &imHeight
|
||||
|
||||
// Bits.
|
||||
xobj.BitsPerComponent = &img.BitsPerComponent
|
||||
// Bits per Component.
|
||||
imBPC := img.BitsPerComponent
|
||||
xobj.BitsPerComponent = &imBPC
|
||||
|
||||
xobj.Filter = encoder
|
||||
xobj.Stream = encoded
|
||||
|
||||
// Guess colorspace if not explicitly set.
|
||||
if cs == nil {
|
||||
@ -284,6 +285,7 @@ func UpdateXObjectImageFromImage(xobjIn *XObjectImage, img *Image, cs PdfColorsp
|
||||
// Has same width and height as original and stored in same
|
||||
// bits per component (1 component, hence the DeviceGray channel).
|
||||
smask := NewXObjectImage()
|
||||
|
||||
smask.Filter = encoder
|
||||
encoded, err := encoder.EncodeBytes(img.alphaData)
|
||||
if err != nil {
|
||||
@ -291,7 +293,7 @@ func UpdateXObjectImageFromImage(xobjIn *XObjectImage, img *Image, cs PdfColorsp
|
||||
return nil, err
|
||||
}
|
||||
smask.Stream = encoded
|
||||
smask.BitsPerComponent = &img.BitsPerComponent
|
||||
smask.BitsPerComponent = xobj.BitsPerComponent
|
||||
smask.Width = &img.Width
|
||||
smask.Height = &img.Height
|
||||
smask.ColorSpace = NewPdfColorspaceDeviceGray()
|
||||
@ -448,6 +450,8 @@ func NewXObjectImageFromStream(stream *core.PdfObjectStream) (*XObjectImage, err
|
||||
|
||||
// SetImage updates XObject Image with new image data.
|
||||
func (ximg *XObjectImage) SetImage(img *Image, cs PdfColorspace) error {
|
||||
// update image parameters of the filter encoder.
|
||||
ximg.Filter.UpdateParams(img.GetParamsDict())
|
||||
encoded, err := ximg.Filter.EncodeBytes(img.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -493,6 +497,7 @@ func (ximg *XObjectImage) SetFilter(encoder core.StreamEncoder) error {
|
||||
}
|
||||
|
||||
ximg.Filter = encoder
|
||||
encoder.UpdateParams(ximg.getParamsDict())
|
||||
encoded, err = encoder.EncodeBytes(decoded)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -596,3 +601,13 @@ func (ximg *XObjectImage) ToPdfObject() core.PdfObject {
|
||||
|
||||
return stream
|
||||
}
|
||||
|
||||
// getParamsDict returns *core.PdfObjectDictionary with a set of basic image parameters.
|
||||
func (ximg *XObjectImage) getParamsDict() *core.PdfObjectDictionary {
|
||||
params := core.MakeDict()
|
||||
params.Set("Width", core.MakeInteger(*ximg.Width))
|
||||
params.Set("Height", core.MakeInteger(*ximg.Height))
|
||||
params.Set("ColorComponents", core.MakeInteger(int64(ximg.ColorSpace.GetNumComponents())))
|
||||
params.Set("BitsPerComponent", core.MakeInteger(*ximg.BitsPerComponent))
|
||||
return params
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user