2016-07-25 14:06:37 +00:00
|
|
|
/*
|
|
|
|
* This file is subject to the terms and conditions defined in
|
2016-07-29 17:23:39 +00:00
|
|
|
* file 'LICENSE.md', which is part of this source code package.
|
2016-07-25 14:06:37 +00:00
|
|
|
*/
|
|
|
|
|
2016-09-08 17:53:45 +00:00
|
|
|
package model
|
2016-07-25 14:06:37 +00:00
|
|
|
|
|
|
|
import (
|
2017-03-04 17:27:23 +00:00
|
|
|
"errors"
|
2017-02-24 17:38:41 +00:00
|
|
|
goimage "image"
|
2017-03-04 17:27:23 +00:00
|
|
|
gocolor "image/color"
|
2016-07-25 14:06:37 +00:00
|
|
|
"image/draw"
|
|
|
|
_ "image/gif"
|
|
|
|
_ "image/png"
|
|
|
|
"io"
|
|
|
|
|
|
|
|
"github.com/unidoc/unidoc/common"
|
2017-03-04 17:27:23 +00:00
|
|
|
. "github.com/unidoc/unidoc/pdf/core"
|
2017-02-22 21:10:57 +00:00
|
|
|
"github.com/unidoc/unidoc/pdf/model/sampling"
|
2016-07-25 14:06:37 +00:00
|
|
|
)
|
|
|
|
|
2017-02-22 21:10:57 +00:00
|
|
|
// Basic representation of an image.
|
|
|
|
// The colorspace is not specified, but must be known when handling the image.
|
2016-07-25 14:06:37 +00:00
|
|
|
type Image struct {
|
2017-02-22 21:10:57 +00:00
|
|
|
Width int64 // The width of the image in samples
|
|
|
|
Height int64 // The height of the image in samples
|
|
|
|
BitsPerComponent int64 // The number of bits per color component
|
2017-03-04 17:27:23 +00:00
|
|
|
ColorComponents int // Color components per pixel
|
2017-02-22 21:10:57 +00:00
|
|
|
Data []byte // Image data stored as bytes.
|
2017-03-05 22:42:30 +00:00
|
|
|
|
2017-06-06 21:16:55 +00:00
|
|
|
// Transparency data: alpha channel.
|
|
|
|
// Stored in same bits per component as original data with 1 color component.
|
|
|
|
alphaData []byte // Alpha channel data.
|
|
|
|
hasAlpha bool // Indicates whether the alpha channel data is available.
|
|
|
|
|
2017-03-05 22:42:30 +00:00
|
|
|
decode []float64 // [Dmin Dmax ... values for each color component]
|
2017-02-22 21:10:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Convert 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.
|
|
|
|
func (this *Image) GetSamples() []uint32 {
|
|
|
|
samples := sampling.ResampleBytes(this.Data, int(this.BitsPerComponent))
|
2017-03-04 17:27:23 +00:00
|
|
|
|
|
|
|
expectedLen := int(this.Width) * int(this.Height) * this.ColorComponents
|
|
|
|
if len(samples) < expectedLen {
|
|
|
|
// Return error, or fill with 0s?
|
|
|
|
common.Log.Debug("Error: Too few samples (got %d, expecting %d)", len(samples), expectedLen)
|
|
|
|
return samples
|
|
|
|
} else if len(samples) > expectedLen {
|
|
|
|
samples = samples[:expectedLen]
|
|
|
|
}
|
2017-02-22 21:10:57 +00:00
|
|
|
return samples
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert samples to byte-data.
|
|
|
|
func (this *Image) SetSamples(samples []uint32) {
|
2017-02-24 17:38:41 +00:00
|
|
|
resampled := sampling.ResampleUint32(samples, int(this.BitsPerComponent), 8)
|
2017-02-22 21:10:57 +00:00
|
|
|
data := []byte{}
|
|
|
|
for _, val := range resampled {
|
|
|
|
data = append(data, byte(val))
|
|
|
|
}
|
2017-02-24 17:38:41 +00:00
|
|
|
|
2017-02-22 21:10:57 +00:00
|
|
|
this.Data = data
|
2016-07-25 14:06:37 +00:00
|
|
|
}
|
|
|
|
|
2017-03-04 17:27:23 +00:00
|
|
|
func (this *Image) ToGoImage() (goimage.Image, error) {
|
|
|
|
common.Log.Trace("Converting to go image")
|
|
|
|
bounds := goimage.Rect(0, 0, int(this.Width), int(this.Height))
|
|
|
|
var img DrawableImage
|
2017-03-08 16:50:34 +00:00
|
|
|
|
2017-03-04 17:27:23 +00:00
|
|
|
if this.ColorComponents == 1 {
|
|
|
|
if this.BitsPerComponent == 16 {
|
|
|
|
img = goimage.NewGray16(bounds)
|
|
|
|
} else {
|
|
|
|
img = goimage.NewGray(bounds)
|
|
|
|
}
|
|
|
|
} else if this.ColorComponents == 3 {
|
|
|
|
if this.BitsPerComponent == 16 {
|
|
|
|
img = goimage.NewRGBA64(bounds)
|
|
|
|
} else {
|
|
|
|
img = goimage.NewRGBA(bounds)
|
|
|
|
}
|
|
|
|
} else if this.ColorComponents == 4 {
|
|
|
|
img = goimage.NewCMYK(bounds)
|
|
|
|
} else {
|
|
|
|
// XXX? Force RGB convert?
|
|
|
|
common.Log.Debug("Unsupported number of colors components per sample: %d", this.ColorComponents)
|
|
|
|
return nil, errors.New("Unsupported colors")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Draw the data on the image..
|
|
|
|
x := 0
|
|
|
|
y := 0
|
2017-06-06 21:16:55 +00:00
|
|
|
aidx := 0
|
2017-03-04 17:27:23 +00:00
|
|
|
|
|
|
|
samples := this.GetSamples()
|
|
|
|
//bytesPerColor := colorComponents * int(this.BitsPerComponent) / 8
|
|
|
|
bytesPerColor := this.ColorComponents
|
|
|
|
for i := 0; i+bytesPerColor-1 < len(samples); i += bytesPerColor {
|
|
|
|
var c gocolor.Color
|
|
|
|
if this.ColorComponents == 1 {
|
|
|
|
if this.BitsPerComponent == 16 {
|
|
|
|
val := uint16(samples[i])<<8 | uint16(samples[i+1])
|
|
|
|
c = gocolor.Gray16{val}
|
|
|
|
} else {
|
|
|
|
val := uint8(samples[i] & 0xff)
|
|
|
|
c = gocolor.Gray{val}
|
|
|
|
}
|
|
|
|
} else if this.ColorComponents == 3 {
|
|
|
|
if this.BitsPerComponent == 16 {
|
|
|
|
r := uint16(samples[i])<<8 | uint16(samples[i+1])
|
|
|
|
g := uint16(samples[i+2])<<8 | uint16(samples[i+3])
|
|
|
|
b := uint16(samples[i+4])<<8 | uint16(samples[i+5])
|
2017-06-06 21:16:55 +00:00
|
|
|
a := uint16(0)
|
|
|
|
if this.alphaData != nil && len(this.alphaData) > aidx+1 {
|
|
|
|
a = uint16(this.alphaData[aidx]<<8) | uint16(this.alphaData[aidx+1])
|
|
|
|
aidx += 2
|
|
|
|
}
|
|
|
|
c = gocolor.RGBA64{R: r, G: g, B: b, A: a}
|
2017-03-04 17:27:23 +00:00
|
|
|
} else {
|
|
|
|
r := uint8(samples[i] & 0xff)
|
|
|
|
g := uint8(samples[i+1] & 0xff)
|
|
|
|
b := uint8(samples[i+2] & 0xff)
|
2017-06-06 21:16:55 +00:00
|
|
|
a := uint8(0)
|
|
|
|
if this.alphaData != nil && len(this.alphaData) > aidx {
|
|
|
|
a = uint8(this.alphaData[aidx])
|
|
|
|
aidx++
|
|
|
|
}
|
|
|
|
c = gocolor.RGBA{R: r, G: g, B: b, A: a}
|
2017-03-04 17:27:23 +00:00
|
|
|
}
|
|
|
|
} else if this.ColorComponents == 4 {
|
|
|
|
c1 := uint8(samples[i] & 0xff)
|
|
|
|
m1 := uint8(samples[i+1] & 0xff)
|
|
|
|
y1 := uint8(samples[i+2] & 0xff)
|
|
|
|
k1 := uint8(samples[i+3] & 0xff)
|
|
|
|
c = gocolor.CMYK{C: c1, M: m1, Y: y1, K: k1}
|
|
|
|
}
|
|
|
|
|
|
|
|
img.Set(x, y, c)
|
|
|
|
x++
|
|
|
|
if x == int(this.Width) {
|
|
|
|
x = 0
|
|
|
|
y++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return img, nil
|
|
|
|
}
|
|
|
|
|
2016-07-25 14:06:37 +00:00
|
|
|
type ImageHandler interface {
|
|
|
|
// Read any image type and load into a new Image object.
|
|
|
|
Read(r io.Reader) (*Image, error)
|
|
|
|
// Compress an image.
|
|
|
|
Compress(input *Image, quality int64) (*Image, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Default implementation.
|
|
|
|
|
|
|
|
type DefaultImageHandler struct{}
|
|
|
|
|
2017-02-22 21:10:57 +00:00
|
|
|
// Reads an image and loads into a new Image object with an RGB
|
|
|
|
// colormap and 8 bits per component.
|
2016-07-25 14:06:37 +00:00
|
|
|
func (this DefaultImageHandler) Read(reader io.Reader) (*Image, error) {
|
|
|
|
// Load the image with the native implementation.
|
2017-02-24 17:38:41 +00:00
|
|
|
img, _, err := goimage.Decode(reader)
|
2016-07-25 14:06:37 +00:00
|
|
|
if err != nil {
|
2016-10-31 21:48:25 +00:00
|
|
|
common.Log.Debug("Error decoding file: %s", err)
|
2016-07-25 14:06:37 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Speed up jpeg encoding by converting to RGBA first.
|
|
|
|
// Will not be required once the golang image/jpeg package is optimized.
|
|
|
|
b := img.Bounds()
|
2017-02-24 17:38:41 +00:00
|
|
|
m := goimage.NewRGBA(goimage.Rect(0, 0, b.Dx(), b.Dy()))
|
2016-07-25 14:06:37 +00:00
|
|
|
draw.Draw(m, m.Bounds(), img, b.Min, draw.Src)
|
2017-03-08 16:50:34 +00:00
|
|
|
|
2017-06-06 21:16:55 +00:00
|
|
|
alphaData := []byte{}
|
|
|
|
hasAlpha := false
|
|
|
|
|
2017-03-08 16:50:34 +00:00
|
|
|
data := []byte{}
|
|
|
|
for i := 0; i < len(m.Pix); i += 4 {
|
|
|
|
data = append(data, m.Pix[i], m.Pix[i+1], m.Pix[i+2])
|
2017-06-06 21:16:55 +00:00
|
|
|
|
|
|
|
alpha := m.Pix[i+3]
|
|
|
|
if alpha != 0 {
|
|
|
|
hasAlpha = true
|
|
|
|
}
|
|
|
|
alphaData = append(alphaData, alpha)
|
2016-07-25 14:06:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
imag := Image{}
|
|
|
|
imag.Width = int64(b.Dx())
|
|
|
|
imag.Height = int64(b.Dy())
|
2017-02-22 21:10:57 +00:00
|
|
|
imag.BitsPerComponent = 8 // RGBA colormap
|
2017-03-08 16:50:34 +00:00
|
|
|
imag.ColorComponents = 3
|
|
|
|
imag.Data = data // buf.Bytes()
|
2016-07-25 14:06:37 +00:00
|
|
|
|
2017-06-06 21:16:55 +00:00
|
|
|
imag.hasAlpha = hasAlpha
|
|
|
|
if hasAlpha {
|
|
|
|
imag.alphaData = alphaData
|
|
|
|
}
|
|
|
|
|
2016-07-25 14:06:37 +00:00
|
|
|
return &imag, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// To be implemented.
|
2017-03-04 17:27:23 +00:00
|
|
|
// Should be able to compress in terms of JPEG quality parameter,
|
|
|
|
// and DPI threshold (need to know bounding area dimensions).
|
2016-07-25 14:06:37 +00:00
|
|
|
func (this DefaultImageHandler) Compress(input *Image, quality int64) (*Image, error) {
|
|
|
|
return input, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var ImageHandling ImageHandler = DefaultImageHandler{}
|
|
|
|
|
|
|
|
func SetImageHandler(imgHandling ImageHandler) {
|
|
|
|
ImageHandling = imgHandling
|
|
|
|
}
|