Minor image profiling tweaks.

Fixed minor and major issues with the naming and comments.
Added licenses information to files.
This commit is contained in:
Jacek Kucharczyk 2020-07-15 11:37:05 +02:00
parent bad2551692
commit f0646b6f1f
42 changed files with 219 additions and 73 deletions

View File

@ -124,7 +124,7 @@ func (proc *ContentStreamProcessor) getColorspace(name string, resources *model.
return model.NewPdfColorspaceSpecialPattern(), nil
}
// ReadSample check the colorspace dictionary.
// Next check the colorspace dictionary.
cs, has := resources.GetColorspaceByName(core.PdfObjectName(name))
if has {
return cs, nil

View File

@ -188,7 +188,7 @@ func newFlateEncoderFromStream(streamObj *PdfObjectStream, decodeParams *PdfObje
return encoder, nil
}
encoder.img = encDict.extractImage()
encoder.img = dictExtractImage(encDict)
// If decodeParams not provided, see if we can get from the stream.
if decodeParams == nil {
@ -2087,7 +2087,7 @@ func newMultiEncoderFromStream(streamObj *PdfObjectStream) (*MultiEncoder, error
dParams = dict
}
common.Log.Trace("ReadSample name: %s, dp: %v, dParams: %v", *name, dp, dParams)
common.Log.Trace("Next name: %s, dp: %v, dParams: %v", *name, dp, dParams)
if *name == StreamEncodingFilterNameFlate {
// TODO: need to separate out the DecodeParms..
encoder, err := newFlateEncoderFromStream(streamObj, dParams)

View File

@ -1540,7 +1540,7 @@ func (parser *PdfParser) ParseIndirectObject() (PdfObject, error) {
nextObjectOffset := parser.xrefNextObjectOffset(streamStartOffset)
if streamStartOffset+int64(streamLength) > nextObjectOffset && nextObjectOffset > streamStartOffset {
common.Log.Debug("Expected ending at %d", streamStartOffset+int64(streamLength))
common.Log.Debug("ReadSample object starting at %d", nextObjectOffset)
common.Log.Debug("Next object starting at %d", nextObjectOffset)
// endstream + "\n" endobj + "\n" (17)
newLength := nextObjectOffset - streamStartOffset - 17
if newLength < 0 {

View File

@ -1035,7 +1035,7 @@ func (streams *PdfObjectStreams) WriteString() string {
return b.String()
}
func (d *PdfObjectDictionary) extractImage() (img *imageutil.ImageBase) {
func dictExtractImage(d *PdfObjectDictionary) (img *imageutil.ImageBase) {
var (
integer *PdfObjectInteger
ok bool

View File

@ -177,7 +177,7 @@ func removeLastRune(text string) string {
}
// getSpace returns the space to insert between lines of depth `depth1` and `depth2`.
// ReadSample line is the same depth so it's the same line as this one in the extracted text
// Next line is the same depth so it's the same line as this one in the extracted text
func getSpace(depth1, depth2 float64) string {
eol := !isZero(depth1 - depth2)
if eol {

View File

@ -45,7 +45,7 @@ func NewSubstreamReader(r StreamReader, offset, length uint64) (*SubstreamReader
return nil, errors.New("root reader is nil")
}
common.Log.Trace("NewReader substream at offset: %d with length: %d", offset, length)
common.Log.Trace("NewSubstreamReader at offset: %d with length: %d", offset, length)
return &SubstreamReader{
wrapped: r,
offset: offset,

View File

@ -544,7 +544,48 @@ func TestWrite(t *testing.T) {
})
}
//
// TestWriterWriteBits
func TestWriterWriteBits(t *testing.T) {
t.Run("NonMSB", func(t *testing.T) {
w := NewWriter(make([]byte, 2))
// having empty buffered MSB.
n, err := w.WriteBits(0xb, 4)
require.NoError(t, err)
assert.Zero(t, n)
assert.Equal(t, byte(0xb), w.data[0])
n, err = w.WriteBits(0xdf, 8)
require.NoError(t, err)
assert.Equal(t, 1, n)
assert.Equal(t, byte(0xd), w.data[1])
assert.Equal(t, byte(0xfb), w.data[0])
})
t.Run("MSB", func(t *testing.T) {
w := NewWriterMSB(make([]byte, 2))
n, err := w.WriteBits(0xf, 4)
require.NoError(t, err)
assert.Zero(t, n)
// the output now should be
// 11110000
// ^
assert.Equal(t, byte(0xf0), w.data[0], "%08b", w.data[0])
// write 10111 = 0x17, 5
n, err = w.WriteBits(0x17, 5)
require.NoError(t, err)
// current output should be
// 11111011 10000000
// ^
assert.Equal(t, byte(0xfb), w.data[0])
assert.Equal(t, byte(0x80), w.data[1])
assert.Equal(t, uint8(1), w.bitIndex)
})
}

View File

@ -1,3 +1,8 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package imageutil
import (

View File

@ -1,3 +1,8 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package imageutil
import (

View File

@ -1,3 +1,8 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package imageutil
import (
@ -6,7 +11,7 @@ import (
"image/color"
)
// ColorConverter is an enum that defines image color colorConverter.
// ColorConverter is an interface that allows to convert images between different color spaces.
type ColorConverter interface {
// Convert converts images to given color colorConverter.
Convert(src image.Image) (Image, error)
@ -34,7 +39,7 @@ var (
CMYKConverter = ConverterFunc(cmykConverter)
)
// Models
// Additional models for images.
var (
Gray2Model = color.ModelFunc(gray2Model)
Gray4Model = color.ModelFunc(gray4Model)

View File

@ -1,3 +1,8 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package imageutil
import (

12
internal/imageutil/doc.go Normal file
View File

@ -0,0 +1,12 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
// Package imageutil provides utility functions, structures and interfaces that allows to operate on images.
// The function NewImage creates a new Image implementation based on provided input parameters.
// The functions ColorXXXAt allows to get the the colors from provided data and parameters without the need of creating
// image abstraction.
// Image converters allows to convert between different color spaces. As Image interface implements also image.Image
// it was possible to use standard golang image.Image as an input for the converters.
package imageutil

View File

@ -1,3 +1,8 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package imageutil
import (
@ -11,7 +16,7 @@ func init() {
makePixelSumAtByte()
}
// Gray is an interface used for creating histograms for given image.
// Gray is an interface that allows to get and set color.Gray for grayscale images.
type Gray interface {
GrayAt(x, y int) color.Gray
SetGray(x, y int, g color.Gray)
@ -111,7 +116,7 @@ func (m *Monochrome) Copy() Image {
// Compile time check if Monochrome implements image.Image interface.
var _ image.Image = &Monochrome{}
// At implements image.Image interface
// At implements image.Image interface.
func (m *Monochrome) At(x, y int) color.Color {
c, _ := m.ColorAt(x, y)
return c
@ -173,7 +178,7 @@ func (m *Monochrome) Validate() error {
// Compile time check if Monochrome implements Gray interface.
var _ Gray = &Monochrome{}
// Histogram implements Gray interface.
// Histogram implements Histogramer interface.
func (m *Monochrome) Histogram() (histogram [256]int) {
for _, bt := range m.Data {
histogram[0xff] += int(tab8[m.Data[bt]])
@ -229,12 +234,7 @@ func ColorAtGray1BPC(x, y, bytesPerLine int, data []byte, decode []float64) (col
return color.Gray{Y: c * 255}, nil
}
// Compile time check if Gray2 implements Image interface.
var (
_ Gray = &Gray2{}
)
// Gray2 is an image
// Gray2 is a 2-bit base grayscale image. It implements image.Image, draw.Image, Image and Gray interfaces.
type Gray2 struct {
ImageBase
}
@ -285,7 +285,8 @@ func (i *Gray2) Validate() error {
// Gray2 image.Image methods.
//
// Compile time check if Gray2 implements Gray interface.
// Compile time check if Gray2 implements image.Image interface.
var _ image.Image = &Gray2{}
// At implements image.Image interface.
func (i *Gray2) At(x, y int) color.Color {
@ -319,6 +320,9 @@ func gray2ModelGray(g color.Gray) color.Gray {
// Gray2 - Gray interface.
//
// Compile time check if Gray2 implements Gray interface.
var _ Gray = &Gray2{}
// GrayAt implements Gray interface.
func (i *Gray2) GrayAt(x, y int) color.Gray {
c, _ := ColorAtGray2BPC(x, y, i.BytesPerLine, i.Data, i.Decode)
@ -337,6 +341,16 @@ func (i *Gray2) SetGray(x, y int, gray color.Gray) {
i.Data[index] = (i.Data[index] & (^(0xc0 >> uint(2*((x)&3))))) | (value << uint(6-2*(x&3)))
}
// Histogram implements Histogramer interface.
func (i *Gray2) Histogram() (histogram [256]int) {
for x := 0; x < i.Width; x++ {
for y := 0; y < i.Height; y++ {
histogram[i.GrayAt(x, y).Y]++
}
}
return histogram
}
// ColorAtGray2BPC gets the color of image in grayscale color space with two bits per component specific 'width',
// 'bytesPerLine' and 'data' at the 'x' and 'y' coordinates position.
func ColorAtGray2BPC(x, y, bytesPerLine int, data []byte, decode []float64) (color.Gray, error) {
@ -413,7 +427,7 @@ func (i *Gray4) Validate() error {
// Gray4 image.Image interface methods.
//
// Compile time check if Gray4 implements Gray interface.
// Compile time check if Gray4 implements image.Image interface.
var _ image.Image = &Gray4{}
// At implements image.Image interface.
@ -445,7 +459,7 @@ func (i *Gray4) GrayAt(x, y int) color.Gray {
return c
}
// Histogram implements Gray interface.
// Histogram implements Histogramer interface.
func (i *Gray4) Histogram() (histogram [256]int) {
for x := 0; x < i.Width; x++ {
for y := 0; y < i.Height; y++ {
@ -587,7 +601,7 @@ func (i *Gray8) ColorModel() color.Model {
// Compile time check if Gray8 implements Gray interface.
var _ Gray = &Gray8{}
// Histogram creates histogram based on the given image.
// Histogram implements Histogramer interface.
func (i *Gray8) Histogram() (histogram [256]int) {
for j := 0; j < len(i.Data); j++ {
histogram[i.Data[j]]++
@ -718,7 +732,7 @@ func (i *Gray16) GrayAt(x, y int) color.Gray {
return color.Gray{Y: uint8(c.(color.Gray16).Y >> 8)}
}
// Histogram implements Gray interface.
// Histogram implements Histogramer interface.
func (i *Gray16) Histogram() (histogram [256]int) {
for x := 0; x < i.Width; x++ {
for y := 0; y < i.Height; y++ {

View File

@ -1,3 +1,8 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package imageutil
import (

View File

@ -1,3 +1,8 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package imageutil
import (
@ -103,9 +108,7 @@ func NewImage(width, height, bitsPerComponent, colorComponents int, data, alpha
if img == nil {
return nil, ErrInvalidImage
}
// if err := img.Validate(); err != nil {
// return nil, err
// }
return img, nil
}

View File

@ -1,10 +1,15 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package imageutil
import (
"math"
)
// LinearInterpolate is the simple linear interpolation from the PDF manual.
// LinearInterpolate is the simple linear interpolation from the PDF manual - PDF 32000-1:2008 - 7.10.2.
func LinearInterpolate(x, xmin, xmax, ymin, ymax float64) float64 {
if math.Abs(xmax-xmin) < 0.000001 {
return ymin

View File

@ -1,3 +1,8 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package imageutil
import (

View File

@ -1,3 +1,8 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package imageutil
import (

View File

@ -1,3 +1,8 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package imageutil
import (

View File

@ -1,3 +1,8 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package imageutil
import (
@ -92,19 +97,27 @@ func AutoThresholdTriangle(histogram [256]int) uint8 {
return uint8(split)
}
// GrayHistogram gets histogram for the provided Gray8 'img'.
// Histogramer is an interface that allows to get a histogram from the image.
type Histogramer interface {
Histogram() [256]int
}
// GrayHistogram gets histogram for the provided Gray image.
func GrayHistogram(g Gray) (histogram [256]int) {
img, ok := g.(image.Image)
if !ok {
switch img := g.(type) {
case Histogramer:
return img.Histogram()
case image.Image:
bounds := img.Bounds()
for x := 0; x < bounds.Max.X; x++ {
for y := 0; y < bounds.Max.Y; y++ {
histogram[g.GrayAt(x, y).Y]++
}
}
return histogram
default:
return [256]int{}
}
bounds := img.Bounds()
for x := 0; x < bounds.Max.X; x++ {
for y := 0; y < bounds.Max.Y; y++ {
histogram[g.GrayAt(x, y).Y]++
}
}
return histogram
}
// ImgToBinary gets the binary (black/white) image from the given image 'i' and provided threshold 'threshold'

View File

@ -56,7 +56,7 @@ type Bitmap struct {
XResolution, YResolution int
}
// NewReader creates new bitmap with the parameters as provided in the arguments.
// New creates new bitmap with the parameters as provided in the arguments.
func New(width, height int) *Bitmap {
bm := newBitmap(width, height)
bm.Data = make([]byte, height*bm.RowStride)

View File

@ -51,7 +51,7 @@ type Decoder struct {
streamPosition int64
}
// NewReader creates new arithmetic Decoder.
// New creates new arithmetic Decoder.
func New(r bitwise.StreamReader) (*Decoder, error) {
d := &Decoder{
r: r,

View File

@ -22,7 +22,7 @@ type Decoder struct {
modeTable []*code
}
// NewReader creates new jbig2 mmr decoder for the provided data stream.
// New creates new jbig2 mmr decoder for the provided data stream.
func New(r bitwise.StreamReader, width, height int, dataOffset, dataLength int64) (*Decoder, error) {
m := &Decoder{
width: width,

View File

@ -23,18 +23,18 @@ import (
var fileHeaderID = []byte{0x97, 0x4A, 0x42, 0x32, 0x0D, 0x0A, 0x1A, 0x0A}
// Document is the jbig2 document model containing pages and global segments.
// By creating new document with method NewReader or NewWithGlobals all the jbig2
// By creating new document with method New or NewWithGlobals all the jbig2
// encoded data segment headers are decoded. In order to decode whole
// document, all of it's pages should be decoded using GetBitmap method.
// PDF encoded documents should contains only one Page with the number 1.
type Document struct {
// Pages contains all pages of this document.
Pages map[int]*Page
// NumberOfPagesUnknown defines if the amout of the pages is known.
// NumberOfPagesUnknown defines if the amount of the pages is known.
NumberOfPagesUnknown bool
// NumberOfPages - D.4.3 - Number of pages field (4 bytes). Only presented if NumberOfPagesUnknown is true.
NumberOfPages uint32
// GBUseExtTemplate defines wether extended Template is used.
// GBUseExtTemplate defines whether extended Template is used.
GBUseExtTemplate bool
// SubInputStream is the source data stream wrapped into a SubInputStream.
InputStream bitwise.StreamReader

View File

@ -37,9 +37,9 @@ func TestEncodeRegion(t *testing.T) {
0x00, 0x00, 0x00, 0x1E,
// Second four bytes should be the height - 150 uint32
0x00, 0x00, 0x00, 0x96,
// ReadSample four bytes should be the x location of the region - 40 uint32
// Next four bytes should be the x location of the region - 40 uint32
0x00, 0x00, 0x00, 0x28,
// ReadSample four bytes should be the y location of the region - 35 uint32
// Next four bytes should be the y location of the region - 35 uint32
0x00, 0x00, 0x00, 0x23,
// The last byte should define the region flags
// 5 empty bits and 3 bits for the combination operator

View File

@ -12,8 +12,8 @@ import (
"github.com/stretchr/testify/require"
"github.com/unidoc/unipdf/v3/common"
"github.com/unidoc/unipdf/v3/internal/bitwise"
"github.com/unidoc/unipdf/v3/internal/bitwise"
"github.com/unidoc/unipdf/v3/internal/jbig2/bitmap"
)

View File

@ -12,8 +12,8 @@ import (
"strings"
"github.com/unidoc/unipdf/v3/common"
"github.com/unidoc/unipdf/v3/internal/bitwise"
"github.com/unidoc/unipdf/v3/internal/bitwise"
"github.com/unidoc/unipdf/v3/internal/jbig2/basic"
"github.com/unidoc/unipdf/v3/internal/jbig2/bitmap"
"github.com/unidoc/unipdf/v3/internal/jbig2/decoder/arithmetic"

View File

@ -35,7 +35,7 @@ func initSimilarTemplatesFinder(c *Classer, bms *bitmap.Bitmap) *similarTemplate
}
}
// ReadSample finds next template state.
// Next finds next template state.
func (f *similarTemplatesFinder) Next() int {
var (
desireDH, desireDW, size, templ int

View File

@ -1,3 +1,8 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package sampling
import (

View File

@ -1,3 +1,8 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package sampling
import (

View File

@ -1,3 +1,8 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package sampling
import (

View File

@ -1,3 +1,8 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package sampling
import (

View File

@ -56,7 +56,7 @@ func GlyphToRune(glyph GlyphName) (rune, bool) {
return r, true
}
// ReadSample try all the glyph naming conventions.
// Next try all the glyph naming conventions.
if groups := reUniEncoding.FindStringSubmatch(string(glyph)); groups != nil {
n, err := strconv.ParseInt(groups[1], 16, 32)
if err == nil {

View File

@ -59,7 +59,7 @@ func buildAll() error {
}
}
// ReadSample do these mapping files
// Next do these mapping files
filenames := []string{
"glyphlist.txt",
"texglyphlist.txt",

View File

@ -76,7 +76,7 @@ func (a *PdfAction) ToPdfObject() core.PdfObject {
d.Set("Type", core.MakeName("Action"))
d.SetIfNotNil("S", a.S)
d.SetIfNotNil("ReadSample", a.Next)
d.SetIfNotNil("Next", a.Next)
return container
}
@ -679,7 +679,7 @@ func (r *PdfReader) newPdfActionFromIndirectObject(container *core.PdfIndirectOb
}
}
if obj := d.Get("ReadSample"); obj != nil {
if obj := d.Get("Next"); obj != nil {
action.Next = obj
}

View File

@ -636,7 +636,7 @@ func (a *PdfAppender) Write(w io.Writer) error {
}
}
parent, hasParent = parentDict.Get("Parent").(*core.PdfIndirectObject)
common.Log.Trace("ReadSample parent: %T", parentDict.Get("Parent"))
common.Log.Trace("Next parent: %T", parentDict.Get("Parent"))
}
pDict.Set("Parent", writer.pages)
}

View File

@ -2376,10 +2376,9 @@ func (cs *PdfColorspaceSpecialIndexed) ImageToRGB(img Image) (Image, error) {
w := sampling.NewWriter(dest)
var (
sample uint32
index int
err error
cValues []byte
sample uint32
index int
err error
)
// Convert the indexed data to base color map data.
// for i := 0; i < len(samples); i++ {
@ -2405,10 +2404,9 @@ func (cs *PdfColorspaceSpecialIndexed) ImageToRGB(img Image) (Image, error) {
}
}
cValues = cs.colorLookup[index*N : (index+1)*N]
common.Log.Trace("C Vals: % d", cValues)
for i := range cValues {
if err = w.WriteSample(uint32(cValues[i])); err != nil {
// cValues = cs.colorLookup[index*N : (index+1)*N]
for i := index * N; i < (index+1)*N; i++ {
if err = w.WriteSample(uint32(cs.colorLookup[i])); err != nil {
return img, err
}
}

View File

@ -281,10 +281,10 @@ func (f *PdfFunctionType0) Evaluate(x []float64) ([]float64, error) {
// Fall back to default Encode/Decode params if not set.
encode := f.Encode
if encode == nil {
encode = []float64{}
encode = make([]float64, len(f.Size)*2)
for i := 0; i < len(f.Size); i++ {
encode = append(encode, 0)
encode = append(encode, float64(f.Size[i]-1))
encode[i*2] = 0
encode[i*2+1] = float64(f.Size[i] - 1)
}
}
decode := f.Decode
@ -292,7 +292,7 @@ func (f *PdfFunctionType0) Evaluate(x []float64) ([]float64, error) {
decode = f.Range
}
var indices []int
indices := make([]int, len(x))
// Start with nearest neighbour interpolation.
for i := 0; i < len(x); i++ {
xi := x[i]
@ -315,7 +315,7 @@ func (f *PdfFunctionType0) Evaluate(x []float64) ([]float64, error) {
} else if index > f.Size[i] {
index = f.Size[i] - 1
}
indices = append(indices, index)
indices[i] = index
}
// Calculate the index
@ -338,7 +338,7 @@ func (f *PdfFunctionType0) Evaluate(x []float64) ([]float64, error) {
continue
}
rj := f.data[rjIdx]
rj := f.data[rjIdx]
rjp := imageutil.LinearInterpolate(float64(rj), 0, math.Pow(2, float64(f.BitsPerSample)), decode[2*j], decode[2*j+1])
yj := math.Min(math.Max(rjp, f.Range[2*j]), f.Range[2*j+1])
outputs = append(outputs, yj)

View File

@ -279,7 +279,7 @@ func (oi *PdfOutlineItem) ToPdfObject() core.PdfObject {
dict.Set("Count", core.MakeInteger(*oi.Count))
}
if oi.Next != nil {
dict.Set("ReadSample", oi.Next.ToPdfObject())
dict.Set("Next", oi.Next.ToPdfObject())
}
if oi.First != nil {
dict.Set("First", oi.First.ToPdfObject())

View File

@ -353,12 +353,12 @@ func (r *PdfReader) buildOutlineTree(obj core.PdfObject, parent *PdfOutlineTreeN
}
// Build outline tree for the next item.
nextObj := core.ResolveReference(dict.Get("ReadSample"))
nextObj := core.ResolveReference(dict.Get("Next"))
if _, processed := visited[nextObj]; nextObj != nil && nextObj != container && !processed {
if !core.IsNullObject(nextObj) {
next, last, err := r.buildOutlineTree(nextObj, parent, &outlineItem.PdfOutlineTreeNode, visited)
if err != nil {
common.Log.Debug("DEBUG: could not build outline tree for ReadSample node: %v. Skipping node.", err)
common.Log.Debug("DEBUG: could not build outline tree for Next node: %v. Skipping node.", err)
} else {
outlineItem.Next = next
return &outlineItem.PdfOutlineTreeNode, last, nil

View File

@ -642,7 +642,7 @@ func (w *PdfWriter) AddPage(page *PdfPage) error {
}
}
parent, hasParent = core.GetIndirect(parentDict.Get("Parent"))
common.Log.Trace("ReadSample parent: %T", parentDict.Get("Parent"))
common.Log.Trace("Next parent: %T", parentDict.Get("Parent"))
}
common.Log.Trace("Traversal done")

View File

@ -212,7 +212,7 @@ func (dc *Context) SetRGB255(r, g, b int) {
dc.SetRGBA255(r, g, b, 255)
}
// SetNRGBA sets the current color. r, g, b, a values should be between 0 and 1,
// SetRGBA sets the current color. r, g, b, a values should be between 0 and 1,
// inclusive.
func (dc *Context) SetRGBA(r, g, b, a float64) {
dc.color = color.NRGBA{