diff --git a/pdf/contentstream/inline-image.go b/pdf/contentstream/inline-image.go index 951b239e..7875ff83 100644 --- a/pdf/contentstream/inline-image.go +++ b/pdf/contentstream/inline-image.go @@ -127,6 +127,24 @@ func (this *ContentStreamInlineImage) GetEncoder() (StreamEncoder, error) { return newEncoderFromInlineImage(this) } +// Is a mask ? +// The image mask entry in the image dictionary specifies that the image data shall be used as a stencil +// mask for painting in the current color. The mask data is 1bpc, grayscale. +func (this *ContentStreamInlineImage) IsMask() (bool, error) { + if this.ImageMask != nil { + imMask, ok := this.ImageMask.(*PdfObjectBool) + if !ok { + common.Log.Debug("Image mask not a boolean") + return false, errors.New("Invalid object type") + } + + return bool(*imMask), nil + } else { + return false, nil + } + +} + // Export the inline image to Image which can be transformed or exported easily. // Page resources are needed to look up colorspace information. func (this *ContentStreamInlineImage) ToImage(resources *PdfPageResources) (*Image, error) { @@ -164,30 +182,43 @@ func (this *ContentStreamInlineImage) ToImage(resources *PdfPageResources) (*Ima } image.Width = int64(*width) - // BPC. - if this.BitsPerComponent == nil { - common.Log.Debug("Inline Bits per component missing - assuming 8") - image.BitsPerComponent = 8 - } else { - bpc, ok := this.BitsPerComponent.(*PdfObjectInteger) - if !ok { - common.Log.Debug("Error invalid bits per component value, type %T", this.BitsPerComponent) - return nil, errors.New("BPC Type error") - } - image.BitsPerComponent = int64(*bpc) + // Image mask? + isMask, err := this.IsMask() + if err != nil { + return nil, err } - // Color components. - if this.ColorSpace != nil { - cs, err := this.GetColorSpace(resources) - if err != nil { - return nil, err - } - image.ColorComponents = cs.GetNumComponents() - } else { - // Default gray if not specified. - common.Log.Debug("Inline Image colorspace not specified - assuming 1 color component") + if isMask { + // Masks are grayscale 1bpc. + image.BitsPerComponent = 1 image.ColorComponents = 1 + } else { + + // BPC. + if this.BitsPerComponent == nil { + common.Log.Debug("Inline Bits per component missing - assuming 8") + image.BitsPerComponent = 8 + } else { + bpc, ok := this.BitsPerComponent.(*PdfObjectInteger) + if !ok { + common.Log.Debug("Error invalid bits per component value, type %T", this.BitsPerComponent) + return nil, errors.New("BPC Type error") + } + image.BitsPerComponent = int64(*bpc) + } + + // Color components. + if this.ColorSpace != nil { + cs, err := this.GetColorSpace(resources) + if err != nil { + return nil, err + } + image.ColorComponents = cs.GetNumComponents() + } else { + // Default gray if not specified. + common.Log.Debug("Inline Image colorspace not specified - assuming 1 color component") + image.ColorComponents = 1 + } } image.Data = decoded diff --git a/pdf/model/sampling/resample.go b/pdf/model/sampling/resample.go index 32ac6a85..c32a530b 100644 --- a/pdf/model/sampling/resample.go +++ b/pdf/model/sampling/resample.go @@ -186,5 +186,11 @@ func ResampleUint32(data []uint32, bitsPerInputSample int, bitsPerOutputSample i } } + // If there are partial output samples, pad with 0s. + if bitsLeftPerSample > 0 && bitsLeftPerSample < bitsPerOutputSample { + sample <<= uint(bitsLeftPerSample) + samples = append(samples, sample) + } + return samples } diff --git a/pdf/model/sampling/resample_test.go b/pdf/model/sampling/resample_test.go index f9f93823..fdf8aeae 100644 --- a/pdf/model/sampling/resample_test.go +++ b/pdf/model/sampling/resample_test.go @@ -86,9 +86,10 @@ func TestResamplingBytes(t *testing.T) { } type TestResamplingTest2 struct { - InputData []uint32 - BitsPerSample int - Expected []uint32 + InputData []uint32 + BitsPerInputSample int + BitsPerOutputSample int + Expected []uint32 } // Test resampling example data with different bits per sample. @@ -122,22 +123,40 @@ func TestResamplingUint32(t *testing.T) { // 0xde 0xad 0xbe 0xef 0x15 0x13 0x37 testcases := []TestResamplingTest2{ - {[]uint32{0xB55D2A00}, 1, []uint32{1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, - {[]uint32{0xB55D2A00}, 2, []uint32{2, 3, 1, 1, 1, 1, 3, 1, 0, 2, 2, 2, 0, 0, 0, 0}}, - {[]uint32{0xB55D2A00}, 3, []uint32{5, 5, 2, 5, 6, 4, 5, 2, 0, 0}}, - {[]uint32{0xB55D2A00}, 4, []uint32{11, 5, 5, 13, 2, 10, 0, 0}}, - {[]uint32{0xB55D2A00}, 5, []uint32{22, 21, 14, 18, 20, 0}}, - {[]uint32{0xB55D2A00}, 8, []uint32{0xB5, 0x5D, 0x2A, 0x00}}, - {[]uint32{0xB55D2A00}, 12, []uint32{2901, 3370}}, - {[]uint32{0xB55D2A00}, 13, []uint32{5803, 5288}}, - {[]uint32{0xB55D2A00}, 16, []uint32{0xB55D, 0x2A00}}, - {[]uint32{0xB55D2A00}, 24, []uint32{0xB55D2A}}, - {[]uint32{0xdeadbeef, 0x15133720}, 24, []uint32{0xdeadbe, 0xef1513}}, - {[]uint32{0xdeadbeef, 0x15133720}, 32, []uint32{0xdeadbeef, 0x15133720c}}, + {[]uint32{0xB55D2A00}, 32, 1, []uint32{1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {[]uint32{0xB55D2A00}, 32, 2, []uint32{2, 3, 1, 1, 1, 1, 3, 1, 0, 2, 2, 2, 0, 0, 0, 0}}, + {[]uint32{0xB55D2A00}, 32, 3, []uint32{5, 5, 2, 5, 6, 4, 5, 2, 0, 0}}, + {[]uint32{0xB55D2A00}, 32, 4, []uint32{11, 5, 5, 13, 2, 10, 0, 0}}, + {[]uint32{0xB55D2A00}, 32, 5, []uint32{22, 21, 14, 18, 20, 0}}, + {[]uint32{0xB55D2A00}, 32, 8, []uint32{0xB5, 0x5D, 0x2A, 0x00}}, + {[]uint32{0xB55D2A00}, 32, 12, []uint32{2901, 3370}}, + {[]uint32{0xB55D2A00}, 32, 13, []uint32{5803, 5288}}, + {[]uint32{0xB55D2A00}, 32, 16, []uint32{0xB55D, 0x2A00}}, + {[]uint32{0xB55D2A00}, 32, 24, []uint32{0xB55D2A}}, + {[]uint32{0xdeadbeef, 0x15133720}, 32, 24, []uint32{0xdeadbe, 0xef1513}}, + {[]uint32{0xdeadbeef, 0x15133720}, 32, 32, []uint32{0xdeadbeef, 0x15133720}}, } for _, testcase := range testcases { - b := ResampleUint32(testcase.InputData, 32, testcase.BitsPerSample) + b := ResampleUint32(testcase.InputData, testcase.BitsPerInputSample, testcase.BitsPerOutputSample) + fmt.Println(b) + if !samplesEqual(b, testcase.Expected) { + //t.Errorf("Test case failed. Got: % d, expected: % d", b, testcase.Expected) + t.Errorf("Test case failed. Got: % X, expected: % X", b, testcase.Expected) + } + } +} + +// Test resampling example data with different bits per sample. +// Input is in uint32, certain number of bits. +func TestResamplingUint32xx(t *testing.T) { + testcases := []TestResamplingTest2{ + {[]uint32{0, 0, 0}, 1, 8, []uint32{0}}, + {[]uint32{0, 1, 0}, 1, 8, []uint32{64}}, + } + + for _, testcase := range testcases { + b := ResampleUint32(testcase.InputData, testcase.BitsPerInputSample, testcase.BitsPerOutputSample) fmt.Println(b) if !samplesEqual(b, testcase.Expected) { t.Errorf("Test case failed. Got: % d, expected: % d", b, testcase.Expected)