github.com/unidoc/unidoc@v2.2.0+incompatible/pdf/model/colorspace.go (about)

     1  /*
     2   * This file is subject to the terms and conditions defined in
     3   * file 'LICENSE.md', which is part of this source code package.
     4   */
     5  
     6  package model
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"math"
    12  
    13  	"github.com/unidoc/unidoc/common"
    14  	. "github.com/unidoc/unidoc/pdf/core"
    15  )
    16  
    17  //
    18  // The colorspace defines the data storage format for each color and color representation.
    19  //
    20  // Device based colorspace, specified by name
    21  // - /DeviceGray
    22  // - /DeviceRGB
    23  // - /DeviceCMYK
    24  //
    25  // CIE based colorspace specified by [name, dictionary]
    26  // - [/CalGray dict]
    27  // - [/CalRGB dict]
    28  // - [/Lab dict]
    29  // - [/ICCBased dict]
    30  //
    31  // Special colorspaces
    32  // - /Pattern
    33  // - /Indexed
    34  // - /Separation
    35  // - /DeviceN
    36  //
    37  // Work is in progress to support all colorspaces. At the moment ICCBased color spaces fall back to the alternate
    38  // colorspace which works OK in most cases. For full color support, will need fully featured ICC support.
    39  //
    40  type PdfColorspace interface {
    41  	String() string
    42  	ImageToRGB(Image) (Image, error)
    43  	ColorToRGB(color PdfColor) (PdfColor, error)
    44  	GetNumComponents() int
    45  	ToPdfObject() PdfObject
    46  	ColorFromPdfObjects(objects []PdfObject) (PdfColor, error)
    47  	ColorFromFloats(vals []float64) (PdfColor, error)
    48  
    49  	// Returns the decode array for the CS, i.e. the range of each component.
    50  	DecodeArray() []float64
    51  }
    52  
    53  type PdfColor interface {
    54  }
    55  
    56  // NewPdfColorspaceFromPdfObject loads a PdfColorspace from a PdfObject.  Returns an error if there is
    57  // a failure in loading.
    58  func NewPdfColorspaceFromPdfObject(obj PdfObject) (PdfColorspace, error) {
    59  	var container *PdfIndirectObject
    60  	var csName *PdfObjectName
    61  	var csArray *PdfObjectArray
    62  
    63  	if indObj, isInd := obj.(*PdfIndirectObject); isInd {
    64  		container = indObj
    65  	}
    66  
    67  	// 8.6.3 p. 149 (PDF32000_2008):
    68  	// A colour space shall be defined by an array object whose first element is a name object identifying the
    69  	// colour space family. The remaining array elements, if any, are parameters that further characterize the
    70  	// colour space; their number and types vary according to the particular family.
    71  	//
    72  	// For families that do not require parameters, the colour space may be specified simply by the family name
    73  	// itself instead of an array.
    74  
    75  	obj = TraceToDirectObject(obj)
    76  	switch t := obj.(type) {
    77  	case *PdfObjectArray:
    78  		csArray = t
    79  	case *PdfObjectName:
    80  		csName = t
    81  	}
    82  
    83  	// If specified by a name directly: Device colorspace or Pattern.
    84  	if csName != nil {
    85  		switch *csName {
    86  		case "DeviceGray":
    87  			return NewPdfColorspaceDeviceGray(), nil
    88  		case "DeviceRGB":
    89  			return NewPdfColorspaceDeviceRGB(), nil
    90  		case "DeviceCMYK":
    91  			return NewPdfColorspaceDeviceCMYK(), nil
    92  		case "Pattern":
    93  			return NewPdfColorspaceSpecialPattern(), nil
    94  		default:
    95  			common.Log.Debug("ERROR: Unknown colorspace %s", *csName)
    96  			return nil, ErrRangeError
    97  		}
    98  	}
    99  
   100  	if csArray != nil && len(*csArray) > 0 {
   101  		var csObject PdfObject = container
   102  		if container == nil {
   103  			csObject = csArray
   104  		}
   105  		firstEl := TraceToDirectObject((*csArray)[0])
   106  		if name, isName := firstEl.(*PdfObjectName); isName {
   107  			switch *name {
   108  			case "DeviceGray":
   109  				if len(*csArray) == 1 {
   110  					return NewPdfColorspaceDeviceGray(), nil
   111  				}
   112  			case "DeviceRGB":
   113  				if len(*csArray) == 1 {
   114  					return NewPdfColorspaceDeviceRGB(), nil
   115  				}
   116  			case "DeviceCMYK":
   117  				if len(*csArray) == 1 {
   118  					return NewPdfColorspaceDeviceCMYK(), nil
   119  				}
   120  			case "CalGray":
   121  				return newPdfColorspaceCalGrayFromPdfObject(csObject)
   122  			case "CalRGB":
   123  				return newPdfColorspaceCalRGBFromPdfObject(csObject)
   124  			case "Lab":
   125  				return newPdfColorspaceLabFromPdfObject(csObject)
   126  			case "ICCBased":
   127  				return newPdfColorspaceICCBasedFromPdfObject(csObject)
   128  			case "Pattern":
   129  				return newPdfColorspaceSpecialPatternFromPdfObject(csObject)
   130  			case "Indexed":
   131  				return newPdfColorspaceSpecialIndexedFromPdfObject(csObject)
   132  			case "Separation":
   133  				return newPdfColorspaceSpecialSeparationFromPdfObject(csObject)
   134  			case "DeviceN":
   135  				return newPdfColorspaceDeviceNFromPdfObject(csObject)
   136  			default:
   137  				common.Log.Debug("Array with invalid name: %s", *name)
   138  			}
   139  		}
   140  	}
   141  
   142  	common.Log.Debug("PDF File Error: Colorspace type error: %s", obj.String())
   143  	return nil, ErrTypeCheck
   144  }
   145  
   146  // determine PDF colorspace from a PdfObject.  Returns the colorspace name and an error on failure.
   147  // If the colorspace was not found, will return an empty string.
   148  func determineColorspaceNameFromPdfObject(obj PdfObject) (PdfObjectName, error) {
   149  	var csName *PdfObjectName
   150  	var csArray *PdfObjectArray
   151  
   152  	if indObj, is := obj.(*PdfIndirectObject); is {
   153  		if array, is := indObj.PdfObject.(*PdfObjectArray); is {
   154  			csArray = array
   155  		} else if name, is := indObj.PdfObject.(*PdfObjectName); is {
   156  			csName = name
   157  		}
   158  	} else if array, is := obj.(*PdfObjectArray); is {
   159  		csArray = array
   160  	} else if name, is := obj.(*PdfObjectName); is {
   161  		csName = name
   162  	}
   163  
   164  	// If specified by a name directly: Device colorspace or Pattern.
   165  	if csName != nil {
   166  		switch *csName {
   167  		case "DeviceGray", "DeviceRGB", "DeviceCMYK":
   168  			return *csName, nil
   169  		case "Pattern":
   170  			return *csName, nil
   171  		}
   172  	}
   173  
   174  	if csArray != nil && len(*csArray) > 0 {
   175  		if name, is := (*csArray)[0].(*PdfObjectName); is {
   176  			switch *name {
   177  			case "DeviceGray", "DeviceRGB", "DeviceCMYK":
   178  				if len(*csArray) == 1 {
   179  					return *name, nil
   180  				}
   181  			case "CalGray", "CalRGB", "Lab":
   182  				return *name, nil
   183  			case "ICCBased", "Pattern", "Indexed":
   184  				return *name, nil
   185  			case "Separation", "DeviceN":
   186  				return *name, nil
   187  			}
   188  		}
   189  	}
   190  
   191  	// Not found
   192  	return "", nil
   193  }
   194  
   195  // Gray scale component.
   196  // No specific parameters
   197  
   198  // A grayscale value shall be represented by a single number in the range 0.0 to 1.0 where 0.0 corresponds to black
   199  // and 1.0 to white.
   200  type PdfColorDeviceGray float64
   201  
   202  func NewPdfColorDeviceGray(grayVal float64) *PdfColorDeviceGray {
   203  	color := PdfColorDeviceGray(grayVal)
   204  	return &color
   205  }
   206  
   207  func (this *PdfColorDeviceGray) GetNumComponents() int {
   208  	return 1
   209  }
   210  
   211  func (this *PdfColorDeviceGray) Val() float64 {
   212  	return float64(*this)
   213  }
   214  
   215  // Convert to an integer format.
   216  func (this *PdfColorDeviceGray) ToInteger(bits int) uint32 {
   217  	maxVal := math.Pow(2, float64(bits)) - 1
   218  	return uint32(maxVal * this.Val())
   219  }
   220  
   221  type PdfColorspaceDeviceGray struct{}
   222  
   223  func NewPdfColorspaceDeviceGray() *PdfColorspaceDeviceGray {
   224  	return &PdfColorspaceDeviceGray{}
   225  }
   226  
   227  func (this *PdfColorspaceDeviceGray) GetNumComponents() int {
   228  	return 1
   229  }
   230  
   231  // DecodeArray returns the range of color component values in DeviceGray colorspace.
   232  func (this *PdfColorspaceDeviceGray) DecodeArray() []float64 {
   233  	return []float64{0, 1.0}
   234  }
   235  
   236  func (this *PdfColorspaceDeviceGray) ToPdfObject() PdfObject {
   237  	return MakeName("DeviceGray")
   238  }
   239  
   240  func (this *PdfColorspaceDeviceGray) String() string {
   241  	return "DeviceGray"
   242  }
   243  
   244  func (this *PdfColorspaceDeviceGray) ColorFromFloats(vals []float64) (PdfColor, error) {
   245  	if len(vals) != 1 {
   246  		return nil, errors.New("Range check")
   247  	}
   248  
   249  	val := vals[0]
   250  
   251  	if val < 0.0 || val > 1.0 {
   252  		return nil, errors.New("Range check")
   253  	}
   254  
   255  	return NewPdfColorDeviceGray(val), nil
   256  }
   257  
   258  func (this *PdfColorspaceDeviceGray) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) {
   259  	if len(objects) != 1 {
   260  		return nil, errors.New("Range check")
   261  	}
   262  
   263  	floats, err := getNumbersAsFloat(objects)
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  
   268  	return this.ColorFromFloats(floats)
   269  }
   270  
   271  // Convert gray -> rgb for a single color component.
   272  func (this *PdfColorspaceDeviceGray) ColorToRGB(color PdfColor) (PdfColor, error) {
   273  	gray, ok := color.(*PdfColorDeviceGray)
   274  	if !ok {
   275  		common.Log.Debug("Input color not device gray %T", color)
   276  		return nil, errors.New("Type check error")
   277  	}
   278  
   279  	return NewPdfColorDeviceRGB(float64(*gray), float64(*gray), float64(*gray)), nil
   280  }
   281  
   282  // Convert 1-component grayscale data to 3-component RGB.
   283  func (this *PdfColorspaceDeviceGray) ImageToRGB(img Image) (Image, error) {
   284  	rgbImage := img
   285  
   286  	samples := img.GetSamples()
   287  	common.Log.Trace("DeviceGray-ToRGB Samples: % d", samples)
   288  
   289  	rgbSamples := []uint32{}
   290  	for i := 0; i < len(samples); i++ {
   291  		grayVal := samples[i]
   292  		rgbSamples = append(rgbSamples, grayVal, grayVal, grayVal)
   293  	}
   294  	rgbImage.BitsPerComponent = 8
   295  	rgbImage.ColorComponents = 3
   296  	rgbImage.SetSamples(rgbSamples)
   297  
   298  	common.Log.Trace("DeviceGray -> RGB")
   299  	common.Log.Trace("samples: %v", samples)
   300  	common.Log.Trace("RGB samples: %v", rgbSamples)
   301  	common.Log.Trace("%v -> %v", img, rgbImage)
   302  
   303  	return rgbImage, nil
   304  }
   305  
   306  //////////////////////
   307  // Device RGB
   308  // R, G, B components.
   309  // No specific parameters
   310  
   311  // Each component is defined in the range 0.0 - 1.0 where 1.0 is the primary intensity.
   312  type PdfColorDeviceRGB [3]float64
   313  
   314  func NewPdfColorDeviceRGB(r, g, b float64) *PdfColorDeviceRGB {
   315  	color := PdfColorDeviceRGB{r, g, b}
   316  	return &color
   317  }
   318  
   319  func (this *PdfColorDeviceRGB) GetNumComponents() int {
   320  	return 3
   321  }
   322  
   323  func (this *PdfColorDeviceRGB) R() float64 {
   324  	return float64(this[0])
   325  }
   326  
   327  func (this *PdfColorDeviceRGB) G() float64 {
   328  	return float64(this[1])
   329  }
   330  
   331  func (this *PdfColorDeviceRGB) B() float64 {
   332  	return float64(this[2])
   333  }
   334  
   335  // Convert to an integer format.
   336  func (this *PdfColorDeviceRGB) ToInteger(bits int) [3]uint32 {
   337  	maxVal := math.Pow(2, float64(bits)) - 1
   338  	return [3]uint32{uint32(maxVal * this.R()), uint32(maxVal * this.G()), uint32(maxVal * this.B())}
   339  }
   340  
   341  func (this *PdfColorDeviceRGB) ToGray() *PdfColorDeviceGray {
   342  	// Calculate grayValue [0-1]
   343  	grayValue := 0.3*this.R() + 0.59*this.G() + 0.11*this.B()
   344  
   345  	// Clip to [0-1]
   346  	grayValue = math.Min(math.Max(grayValue, 0.0), 1.0)
   347  
   348  	return NewPdfColorDeviceGray(grayValue)
   349  }
   350  
   351  // RGB colorspace.
   352  
   353  type PdfColorspaceDeviceRGB struct{}
   354  
   355  func NewPdfColorspaceDeviceRGB() *PdfColorspaceDeviceRGB {
   356  	return &PdfColorspaceDeviceRGB{}
   357  }
   358  
   359  func (this *PdfColorspaceDeviceRGB) String() string {
   360  	return "DeviceRGB"
   361  }
   362  
   363  func (this *PdfColorspaceDeviceRGB) GetNumComponents() int {
   364  	return 3
   365  }
   366  
   367  // DecodeArray returns the range of color component values in DeviceRGB colorspace.
   368  func (this *PdfColorspaceDeviceRGB) DecodeArray() []float64 {
   369  	return []float64{0.0, 1.0, 0.0, 1.0, 0.0, 1.0}
   370  }
   371  
   372  func (this *PdfColorspaceDeviceRGB) ToPdfObject() PdfObject {
   373  	return MakeName("DeviceRGB")
   374  }
   375  
   376  func (this *PdfColorspaceDeviceRGB) ColorFromFloats(vals []float64) (PdfColor, error) {
   377  	if len(vals) != 3 {
   378  		return nil, errors.New("Range check")
   379  	}
   380  
   381  	// Red.
   382  	r := vals[0]
   383  	if r < 0.0 || r > 1.0 {
   384  		return nil, errors.New("Range check")
   385  	}
   386  
   387  	// Green.
   388  	g := vals[1]
   389  	if g < 0.0 || g > 1.0 {
   390  		return nil, errors.New("Range check")
   391  	}
   392  
   393  	// Blue.
   394  	b := vals[2]
   395  	if b < 0.0 || b > 1.0 {
   396  		return nil, errors.New("Range check")
   397  	}
   398  
   399  	color := NewPdfColorDeviceRGB(r, g, b)
   400  	return color, nil
   401  
   402  }
   403  
   404  // Get the color from a series of pdf objects (3 for rgb).
   405  func (this *PdfColorspaceDeviceRGB) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) {
   406  	if len(objects) != 3 {
   407  		return nil, errors.New("Range check")
   408  	}
   409  
   410  	floats, err := getNumbersAsFloat(objects)
   411  	if err != nil {
   412  		return nil, err
   413  	}
   414  
   415  	return this.ColorFromFloats(floats)
   416  }
   417  
   418  func (this *PdfColorspaceDeviceRGB) ColorToRGB(color PdfColor) (PdfColor, error) {
   419  	rgb, ok := color.(*PdfColorDeviceRGB)
   420  	if !ok {
   421  		common.Log.Debug("Input color not device RGB")
   422  		return nil, errors.New("Type check error")
   423  	}
   424  	return rgb, nil
   425  }
   426  
   427  func (this *PdfColorspaceDeviceRGB) ImageToRGB(img Image) (Image, error) {
   428  	return img, nil
   429  }
   430  
   431  func (this *PdfColorspaceDeviceRGB) ImageToGray(img Image) (Image, error) {
   432  	grayImage := img
   433  
   434  	samples := img.GetSamples()
   435  
   436  	maxVal := math.Pow(2, float64(img.BitsPerComponent)) - 1
   437  	graySamples := []uint32{}
   438  	for i := 0; i < len(samples); i += 3 {
   439  		// Normalized data, range 0-1.
   440  		r := float64(samples[i]) / maxVal
   441  		g := float64(samples[i+1]) / maxVal
   442  		b := float64(samples[i+2]) / maxVal
   443  
   444  		// Calculate grayValue [0-1]
   445  		grayValue := 0.3*r + 0.59*g + 0.11*b
   446  
   447  		// Clip to [0-1]
   448  		grayValue = math.Min(math.Max(grayValue, 0.0), 1.0)
   449  
   450  		// Convert to uint32
   451  		val := uint32(grayValue * maxVal)
   452  		graySamples = append(graySamples, val)
   453  	}
   454  	grayImage.SetSamples(graySamples)
   455  	grayImage.ColorComponents = 1
   456  
   457  	return grayImage, nil
   458  }
   459  
   460  //////////////////////
   461  // DeviceCMYK
   462  // C, M, Y, K components.
   463  // No other parameters.
   464  
   465  // Each component is defined in the range 0.0 - 1.0 where 1.0 is the primary intensity.
   466  type PdfColorDeviceCMYK [4]float64
   467  
   468  func NewPdfColorDeviceCMYK(c, m, y, k float64) *PdfColorDeviceCMYK {
   469  	color := PdfColorDeviceCMYK{c, m, y, k}
   470  	return &color
   471  }
   472  
   473  func (this *PdfColorDeviceCMYK) GetNumComponents() int {
   474  	return 4
   475  }
   476  
   477  func (this *PdfColorDeviceCMYK) C() float64 {
   478  	return float64(this[0])
   479  }
   480  
   481  func (this *PdfColorDeviceCMYK) M() float64 {
   482  	return float64(this[1])
   483  }
   484  
   485  func (this *PdfColorDeviceCMYK) Y() float64 {
   486  	return float64(this[2])
   487  }
   488  
   489  func (this *PdfColorDeviceCMYK) K() float64 {
   490  	return float64(this[3])
   491  }
   492  
   493  // Convert to an integer format.
   494  func (this *PdfColorDeviceCMYK) ToInteger(bits int) [4]uint32 {
   495  	maxVal := math.Pow(2, float64(bits)) - 1
   496  	return [4]uint32{uint32(maxVal * this.C()), uint32(maxVal * this.M()), uint32(maxVal * this.Y()), uint32(maxVal * this.K())}
   497  }
   498  
   499  type PdfColorspaceDeviceCMYK struct{}
   500  
   501  func NewPdfColorspaceDeviceCMYK() *PdfColorspaceDeviceCMYK {
   502  	return &PdfColorspaceDeviceCMYK{}
   503  }
   504  
   505  func (this *PdfColorspaceDeviceCMYK) String() string {
   506  	return "DeviceCMYK"
   507  }
   508  
   509  func (this *PdfColorspaceDeviceCMYK) GetNumComponents() int {
   510  	return 4
   511  }
   512  
   513  // DecodeArray returns the range of color component values in DeviceCMYK colorspace.
   514  func (this *PdfColorspaceDeviceCMYK) DecodeArray() []float64 {
   515  	return []float64{0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0}
   516  }
   517  
   518  func (this *PdfColorspaceDeviceCMYK) ToPdfObject() PdfObject {
   519  	return MakeName("DeviceCMYK")
   520  }
   521  
   522  func (this *PdfColorspaceDeviceCMYK) ColorFromFloats(vals []float64) (PdfColor, error) {
   523  	if len(vals) != 4 {
   524  		return nil, errors.New("Range check")
   525  	}
   526  
   527  	// Cyan
   528  	c := vals[0]
   529  	if c < 0.0 || c > 1.0 {
   530  		return nil, errors.New("Range check")
   531  	}
   532  
   533  	// Magenta
   534  	m := vals[1]
   535  	if m < 0.0 || m > 1.0 {
   536  		return nil, errors.New("Range check")
   537  	}
   538  
   539  	// Yellow.
   540  	y := vals[2]
   541  	if y < 0.0 || y > 1.0 {
   542  		return nil, errors.New("Range check")
   543  	}
   544  
   545  	// Key.
   546  	k := vals[3]
   547  	if k < 0.0 || k > 1.0 {
   548  		return nil, errors.New("Range check")
   549  	}
   550  
   551  	color := NewPdfColorDeviceCMYK(c, m, y, k)
   552  	return color, nil
   553  }
   554  
   555  // Get the color from a series of pdf objects (4 for cmyk).
   556  func (this *PdfColorspaceDeviceCMYK) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) {
   557  	if len(objects) != 4 {
   558  		return nil, errors.New("Range check")
   559  	}
   560  
   561  	floats, err := getNumbersAsFloat(objects)
   562  	if err != nil {
   563  		return nil, err
   564  	}
   565  
   566  	return this.ColorFromFloats(floats)
   567  }
   568  
   569  func (this *PdfColorspaceDeviceCMYK) ColorToRGB(color PdfColor) (PdfColor, error) {
   570  	cmyk, ok := color.(*PdfColorDeviceCMYK)
   571  	if !ok {
   572  		common.Log.Debug("Input color not device cmyk")
   573  		return nil, errors.New("Type check error")
   574  	}
   575  
   576  	c := cmyk.C()
   577  	m := cmyk.M()
   578  	y := cmyk.Y()
   579  	k := cmyk.K()
   580  
   581  	c = c*(1-k) + k
   582  	m = m*(1-k) + k
   583  	y = y*(1-k) + k
   584  
   585  	r := 1 - c
   586  	g := 1 - m
   587  	b := 1 - y
   588  
   589  	return NewPdfColorDeviceRGB(r, g, b), nil
   590  }
   591  
   592  func (this *PdfColorspaceDeviceCMYK) ImageToRGB(img Image) (Image, error) {
   593  	rgbImage := img
   594  
   595  	samples := img.GetSamples()
   596  
   597  	common.Log.Trace("CMYK -> RGB")
   598  	common.Log.Trace("image bpc: %d, color comps: %d", img.BitsPerComponent, img.ColorComponents)
   599  	common.Log.Trace("Len data: %d, len samples: %d", len(img.Data), len(samples))
   600  	common.Log.Trace("Height: %d, Width: %d", img.Height, img.Width)
   601  	if len(samples)%4 != 0 {
   602  		//common.Log.Debug("samples: % d", samples)
   603  		common.Log.Debug("Input image: %#v", img)
   604  		common.Log.Debug("CMYK -> RGB fail, len samples: %d", len(samples))
   605  		return img, errors.New("CMYK data not a multiple of 4")
   606  	}
   607  
   608  	decode := img.decode
   609  	if decode == nil {
   610  		decode = []float64{0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0}
   611  	}
   612  	if len(decode) != 8 {
   613  		common.Log.Debug("Invalid decode array (%d): % .3f", len(decode), decode)
   614  		return img, errors.New("Invalid decode array")
   615  	}
   616  	common.Log.Trace("Decode array: % f", decode)
   617  
   618  	maxVal := math.Pow(2, float64(img.BitsPerComponent)) - 1
   619  	common.Log.Trace("MaxVal: %f", maxVal)
   620  	rgbSamples := []uint32{}
   621  	for i := 0; i < len(samples); i += 4 {
   622  		// Normalized c, m, y, k values.
   623  		c := interpolate(float64(samples[i]), 0, maxVal, decode[0], decode[1])
   624  		m := interpolate(float64(samples[i+1]), 0, maxVal, decode[2], decode[3])
   625  		y := interpolate(float64(samples[i+2]), 0, maxVal, decode[4], decode[5])
   626  		k := interpolate(float64(samples[i+3]), 0, maxVal, decode[6], decode[7])
   627  
   628  		c = c*(1-k) + k
   629  		m = m*(1-k) + k
   630  		y = y*(1-k) + k
   631  
   632  		r := 1 - c
   633  		g := 1 - m
   634  		b := 1 - y
   635  
   636  		// Convert to uint32 format.
   637  		R := uint32(r * maxVal)
   638  		G := uint32(g * maxVal)
   639  		B := uint32(b * maxVal)
   640  		//common.Log.Trace("(%f,%f,%f,%f) -> (%f,%f,%f) [%d,%d,%d]", c, m, y, k, r, g, b, R, G, B)
   641  
   642  		rgbSamples = append(rgbSamples, R, G, B)
   643  	}
   644  	rgbImage.SetSamples(rgbSamples)
   645  	rgbImage.ColorComponents = 3
   646  
   647  	return rgbImage, nil
   648  }
   649  
   650  //////////////////////
   651  // CIE based gray level.
   652  // Single component
   653  // Each component is defined in the range 0.0 - 1.0 where 1.0 is the primary intensity.
   654  
   655  type PdfColorCalGray float64
   656  
   657  func NewPdfColorCalGray(grayVal float64) *PdfColorCalGray {
   658  	color := PdfColorCalGray(grayVal)
   659  	return &color
   660  }
   661  
   662  func (this *PdfColorCalGray) GetNumComponents() int {
   663  	return 1
   664  }
   665  
   666  func (this *PdfColorCalGray) Val() float64 {
   667  	return float64(*this)
   668  }
   669  
   670  // Convert to an integer format.
   671  func (this *PdfColorCalGray) ToInteger(bits int) uint32 {
   672  	maxVal := math.Pow(2, float64(bits)) - 1
   673  	return uint32(maxVal * this.Val())
   674  }
   675  
   676  // CalGray color space.
   677  type PdfColorspaceCalGray struct {
   678  	WhitePoint []float64 // [XW, YW, ZW]: Required
   679  	BlackPoint []float64 // [XB, YB, ZB]
   680  	Gamma      float64
   681  
   682  	container *PdfIndirectObject
   683  }
   684  
   685  func NewPdfColorspaceCalGray() *PdfColorspaceCalGray {
   686  	cs := &PdfColorspaceCalGray{}
   687  
   688  	// Set optional parameters to default values.
   689  	cs.BlackPoint = []float64{0.0, 0.0, 0.0}
   690  	cs.Gamma = 1
   691  
   692  	return cs
   693  }
   694  
   695  func (this *PdfColorspaceCalGray) String() string {
   696  	return "CalGray"
   697  }
   698  
   699  func (this *PdfColorspaceCalGray) GetNumComponents() int {
   700  	return 1
   701  }
   702  
   703  // DecodeArray returns the range of color component values in CalGray colorspace.
   704  func (this *PdfColorspaceCalGray) DecodeArray() []float64 {
   705  	return []float64{0.0, 1.0}
   706  }
   707  
   708  func newPdfColorspaceCalGrayFromPdfObject(obj PdfObject) (*PdfColorspaceCalGray, error) {
   709  	cs := NewPdfColorspaceCalGray()
   710  
   711  	// If within an indirect object, then make a note of it.  If we write out the PdfObject later
   712  	// we can reference the same container.  Otherwise is not within a container, but rather
   713  	// a new array.
   714  	if indObj, isIndirect := obj.(*PdfIndirectObject); isIndirect {
   715  		cs.container = indObj
   716  	}
   717  
   718  	obj = TraceToDirectObject(obj)
   719  	array, ok := obj.(*PdfObjectArray)
   720  	if !ok {
   721  		return nil, fmt.Errorf("Type error")
   722  	}
   723  
   724  	if len(*array) != 2 {
   725  		return nil, fmt.Errorf("Invalid CalGray colorspace")
   726  	}
   727  
   728  	// Name.
   729  	obj = TraceToDirectObject((*array)[0])
   730  	name, ok := obj.(*PdfObjectName)
   731  	if !ok {
   732  		return nil, fmt.Errorf("CalGray name not a Name object")
   733  	}
   734  	if *name != "CalGray" {
   735  		return nil, fmt.Errorf("Not a CalGray colorspace")
   736  	}
   737  
   738  	// Dict.
   739  	obj = TraceToDirectObject((*array)[1])
   740  	dict, ok := obj.(*PdfObjectDictionary)
   741  	if !ok {
   742  		return nil, fmt.Errorf("CalGray dict not a Dictionary object")
   743  	}
   744  
   745  	// WhitePoint (Required): [Xw, Yw, Zw]
   746  	obj = dict.Get("WhitePoint")
   747  	obj = TraceToDirectObject(obj)
   748  	whitePointArray, ok := obj.(*PdfObjectArray)
   749  	if !ok {
   750  		return nil, fmt.Errorf("CalGray: Invalid WhitePoint")
   751  	}
   752  	if len(*whitePointArray) != 3 {
   753  		return nil, fmt.Errorf("CalGray: Invalid WhitePoint array")
   754  	}
   755  	whitePoint, err := whitePointArray.GetAsFloat64Slice()
   756  	if err != nil {
   757  		return nil, err
   758  	}
   759  	cs.WhitePoint = whitePoint
   760  
   761  	// BlackPoint (Optional)
   762  	obj = dict.Get("BlackPoint")
   763  	if obj != nil {
   764  		obj = TraceToDirectObject(obj)
   765  		blackPointArray, ok := obj.(*PdfObjectArray)
   766  		if !ok {
   767  			return nil, fmt.Errorf("CalGray: Invalid BlackPoint")
   768  		}
   769  		if len(*blackPointArray) != 3 {
   770  			return nil, fmt.Errorf("CalGray: Invalid BlackPoint array")
   771  		}
   772  		blackPoint, err := blackPointArray.GetAsFloat64Slice()
   773  		if err != nil {
   774  			return nil, err
   775  		}
   776  		cs.BlackPoint = blackPoint
   777  	}
   778  
   779  	// Gamma (Optional)
   780  	obj = dict.Get("Gamma")
   781  	if obj != nil {
   782  		obj = TraceToDirectObject(obj)
   783  		gamma, err := getNumberAsFloat(obj)
   784  		if err != nil {
   785  			return nil, fmt.Errorf("CalGray: gamma not a number")
   786  		}
   787  		cs.Gamma = gamma
   788  	}
   789  
   790  	return cs, nil
   791  }
   792  
   793  // Return as PDF object format [name dictionary]
   794  func (this *PdfColorspaceCalGray) ToPdfObject() PdfObject {
   795  	// CalGray color space dictionary..
   796  	cspace := &PdfObjectArray{}
   797  
   798  	cspace.Append(MakeName("CalGray"))
   799  
   800  	dict := MakeDict()
   801  	if this.WhitePoint != nil {
   802  		dict.Set("WhitePoint", MakeArray(MakeFloat(this.WhitePoint[0]), MakeFloat(this.WhitePoint[1]), MakeFloat(this.WhitePoint[2])))
   803  	} else {
   804  		common.Log.Error("CalGray: Missing WhitePoint (Required)")
   805  	}
   806  
   807  	if this.BlackPoint != nil {
   808  		dict.Set("BlackPoint", MakeArray(MakeFloat(this.BlackPoint[0]), MakeFloat(this.BlackPoint[1]), MakeFloat(this.BlackPoint[2])))
   809  	}
   810  
   811  	dict.Set("Gamma", MakeFloat(this.Gamma))
   812  	cspace.Append(dict)
   813  
   814  	if this.container != nil {
   815  		this.container.PdfObject = cspace
   816  		return this.container
   817  	}
   818  
   819  	return cspace
   820  }
   821  
   822  func (this *PdfColorspaceCalGray) ColorFromFloats(vals []float64) (PdfColor, error) {
   823  	if len(vals) != 1 {
   824  		return nil, errors.New("Range check")
   825  	}
   826  
   827  	val := vals[0]
   828  	if val < 0.0 || val > 1.0 {
   829  		return nil, errors.New("Range check")
   830  	}
   831  
   832  	color := NewPdfColorCalGray(val)
   833  	return color, nil
   834  }
   835  
   836  func (this *PdfColorspaceCalGray) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) {
   837  	if len(objects) != 1 {
   838  		return nil, errors.New("Range check")
   839  	}
   840  
   841  	floats, err := getNumbersAsFloat(objects)
   842  	if err != nil {
   843  		return nil, err
   844  	}
   845  
   846  	return this.ColorFromFloats(floats)
   847  }
   848  
   849  func (this *PdfColorspaceCalGray) ColorToRGB(color PdfColor) (PdfColor, error) {
   850  	calgray, ok := color.(*PdfColorCalGray)
   851  	if !ok {
   852  		common.Log.Debug("Input color not cal gray")
   853  		return nil, errors.New("Type check error")
   854  	}
   855  
   856  	ANorm := calgray.Val()
   857  
   858  	// A -> X,Y,Z
   859  	X := this.WhitePoint[0] * math.Pow(ANorm, this.Gamma)
   860  	Y := this.WhitePoint[1] * math.Pow(ANorm, this.Gamma)
   861  	Z := this.WhitePoint[2] * math.Pow(ANorm, this.Gamma)
   862  
   863  	// X,Y,Z -> rgb
   864  	// http://stackoverflow.com/questions/21576719/how-to-convert-cie-color-space-into-rgb-or-hex-color-code-in-php
   865  	r := 3.240479*X + -1.537150*Y + -0.498535*Z
   866  	g := -0.969256*X + 1.875992*Y + 0.041556*Z
   867  	b := 0.055648*X + -0.204043*Y + 1.057311*Z
   868  
   869  	// Clip.
   870  	r = math.Min(math.Max(r, 0), 1.0)
   871  	g = math.Min(math.Max(g, 0), 1.0)
   872  	b = math.Min(math.Max(b, 0), 1.0)
   873  
   874  	return NewPdfColorDeviceRGB(r, g, b), nil
   875  }
   876  
   877  // A, B, C -> X, Y, Z
   878  func (this *PdfColorspaceCalGray) ImageToRGB(img Image) (Image, error) {
   879  	rgbImage := img
   880  
   881  	samples := img.GetSamples()
   882  	maxVal := math.Pow(2, float64(img.BitsPerComponent)) - 1
   883  
   884  	rgbSamples := []uint32{}
   885  	for i := 0; i < len(samples); i++ {
   886  		// A represents the gray component of calibrated gray space.
   887  		// It shall be in the range 0.0 - 1.0
   888  		ANorm := float64(samples[i]) / maxVal
   889  
   890  		// A -> X,Y,Z
   891  		X := this.WhitePoint[0] * math.Pow(ANorm, this.Gamma)
   892  		Y := this.WhitePoint[1] * math.Pow(ANorm, this.Gamma)
   893  		Z := this.WhitePoint[2] * math.Pow(ANorm, this.Gamma)
   894  
   895  		// X,Y,Z -> rgb
   896  		// http://stackoverflow.com/questions/21576719/how-to-convert-cie-color-space-into-rgb-or-hex-color-code-in-php
   897  		r := 3.240479*X + -1.537150*Y + -0.498535*Z
   898  		g := -0.969256*X + 1.875992*Y + 0.041556*Z
   899  		b := 0.055648*X + -0.204043*Y + 1.057311*Z
   900  
   901  		// Clip.
   902  		r = math.Min(math.Max(r, 0), 1.0)
   903  		g = math.Min(math.Max(g, 0), 1.0)
   904  		b = math.Min(math.Max(b, 0), 1.0)
   905  
   906  		// Convert to uint32.
   907  		R := uint32(r * maxVal)
   908  		G := uint32(g * maxVal)
   909  		B := uint32(b * maxVal)
   910  
   911  		rgbSamples = append(rgbSamples, R, G, B)
   912  	}
   913  	rgbImage.SetSamples(rgbSamples)
   914  	rgbImage.ColorComponents = 3
   915  
   916  	return rgbImage, nil
   917  }
   918  
   919  //////////////////////
   920  // Colorimetric CIE RGB colorspace.
   921  // A, B, C components
   922  // Each component is defined in the range 0.0 - 1.0 where 1.0 is the primary intensity.
   923  
   924  type PdfColorCalRGB [3]float64
   925  
   926  func NewPdfColorCalRGB(a, b, c float64) *PdfColorCalRGB {
   927  	color := PdfColorCalRGB{a, b, c}
   928  	return &color
   929  }
   930  
   931  func (this *PdfColorCalRGB) GetNumComponents() int {
   932  	return 3
   933  }
   934  
   935  func (this *PdfColorCalRGB) A() float64 {
   936  	return float64(this[0])
   937  }
   938  
   939  func (this *PdfColorCalRGB) B() float64 {
   940  	return float64(this[1])
   941  }
   942  
   943  func (this *PdfColorCalRGB) C() float64 {
   944  	return float64(this[2])
   945  }
   946  
   947  // Convert to an integer format.
   948  func (this *PdfColorCalRGB) ToInteger(bits int) [3]uint32 {
   949  	maxVal := math.Pow(2, float64(bits)) - 1
   950  	return [3]uint32{uint32(maxVal * this.A()), uint32(maxVal * this.B()), uint32(maxVal * this.C())}
   951  }
   952  
   953  // A, B, C components
   954  type PdfColorspaceCalRGB struct {
   955  	WhitePoint []float64
   956  	BlackPoint []float64
   957  	Gamma      []float64
   958  	Matrix     []float64 // [XA YA ZA XB YB ZB XC YC ZC] ; default value identity [1 0 0 0 1 0 0 0 1]
   959  	dict       *PdfObjectDictionary
   960  
   961  	container *PdfIndirectObject
   962  }
   963  
   964  // require parameters?
   965  func NewPdfColorspaceCalRGB() *PdfColorspaceCalRGB {
   966  	cs := &PdfColorspaceCalRGB{}
   967  
   968  	// Set optional parameters to default values.
   969  	cs.BlackPoint = []float64{0.0, 0.0, 0.0}
   970  	cs.Gamma = []float64{1.0, 1.0, 1.0}
   971  	cs.Matrix = []float64{1, 0, 0, 0, 1, 0, 0, 0, 1} // Identity matrix.
   972  
   973  	return cs
   974  }
   975  
   976  func (this *PdfColorspaceCalRGB) String() string {
   977  	return "CalRGB"
   978  }
   979  
   980  func (this *PdfColorspaceCalRGB) GetNumComponents() int {
   981  	return 3
   982  }
   983  
   984  // DecodeArray returns the range of color component values in CalRGB colorspace.
   985  func (this *PdfColorspaceCalRGB) DecodeArray() []float64 {
   986  	return []float64{0.0, 1.0, 0.0, 1.0, 0.0, 1.0}
   987  }
   988  
   989  func newPdfColorspaceCalRGBFromPdfObject(obj PdfObject) (*PdfColorspaceCalRGB, error) {
   990  	cs := NewPdfColorspaceCalRGB()
   991  
   992  	if indObj, isIndirect := obj.(*PdfIndirectObject); isIndirect {
   993  		cs.container = indObj
   994  	}
   995  
   996  	obj = TraceToDirectObject(obj)
   997  	array, ok := obj.(*PdfObjectArray)
   998  	if !ok {
   999  		return nil, fmt.Errorf("Type error")
  1000  	}
  1001  
  1002  	if len(*array) != 2 {
  1003  		return nil, fmt.Errorf("Invalid CalRGB colorspace")
  1004  	}
  1005  
  1006  	// Name.
  1007  	obj = TraceToDirectObject((*array)[0])
  1008  	name, ok := obj.(*PdfObjectName)
  1009  	if !ok {
  1010  		return nil, fmt.Errorf("CalRGB name not a Name object")
  1011  	}
  1012  	if *name != "CalRGB" {
  1013  		return nil, fmt.Errorf("Not a CalRGB colorspace")
  1014  	}
  1015  
  1016  	// Dict.
  1017  	obj = TraceToDirectObject((*array)[1])
  1018  	dict, ok := obj.(*PdfObjectDictionary)
  1019  	if !ok {
  1020  		return nil, fmt.Errorf("CalRGB name not a Name object")
  1021  	}
  1022  
  1023  	// WhitePoint (Required): [Xw, Yw, Zw]
  1024  	obj = dict.Get("WhitePoint")
  1025  	obj = TraceToDirectObject(obj)
  1026  	whitePointArray, ok := obj.(*PdfObjectArray)
  1027  	if !ok {
  1028  		return nil, fmt.Errorf("CalRGB: Invalid WhitePoint")
  1029  	}
  1030  	if len(*whitePointArray) != 3 {
  1031  		return nil, fmt.Errorf("CalRGB: Invalid WhitePoint array")
  1032  	}
  1033  	whitePoint, err := whitePointArray.GetAsFloat64Slice()
  1034  	if err != nil {
  1035  		return nil, err
  1036  	}
  1037  	cs.WhitePoint = whitePoint
  1038  
  1039  	// BlackPoint (Optional)
  1040  	obj = dict.Get("BlackPoint")
  1041  	if obj != nil {
  1042  		obj = TraceToDirectObject(obj)
  1043  		blackPointArray, ok := obj.(*PdfObjectArray)
  1044  		if !ok {
  1045  			return nil, fmt.Errorf("CalRGB: Invalid BlackPoint")
  1046  		}
  1047  		if len(*blackPointArray) != 3 {
  1048  			return nil, fmt.Errorf("CalRGB: Invalid BlackPoint array")
  1049  		}
  1050  		blackPoint, err := blackPointArray.GetAsFloat64Slice()
  1051  		if err != nil {
  1052  			return nil, err
  1053  		}
  1054  		cs.BlackPoint = blackPoint
  1055  	}
  1056  
  1057  	// Gamma (Optional)
  1058  	obj = dict.Get("Gamma")
  1059  	if obj != nil {
  1060  		obj = TraceToDirectObject(obj)
  1061  		gammaArray, ok := obj.(*PdfObjectArray)
  1062  		if !ok {
  1063  			return nil, fmt.Errorf("CalRGB: Invalid Gamma")
  1064  		}
  1065  		if len(*gammaArray) != 3 {
  1066  			return nil, fmt.Errorf("CalRGB: Invalid Gamma array")
  1067  		}
  1068  		gamma, err := gammaArray.GetAsFloat64Slice()
  1069  		if err != nil {
  1070  			return nil, err
  1071  		}
  1072  		cs.Gamma = gamma
  1073  	}
  1074  
  1075  	// Matrix (Optional).
  1076  	obj = dict.Get("Matrix")
  1077  	if obj != nil {
  1078  		obj = TraceToDirectObject(obj)
  1079  		matrixArray, ok := obj.(*PdfObjectArray)
  1080  		if !ok {
  1081  			return nil, fmt.Errorf("CalRGB: Invalid Matrix")
  1082  		}
  1083  		if len(*matrixArray) != 9 {
  1084  			common.Log.Error("Matrix array: %s", matrixArray.String())
  1085  			return nil, fmt.Errorf("CalRGB: Invalid Matrix array")
  1086  		}
  1087  		matrix, err := matrixArray.GetAsFloat64Slice()
  1088  		if err != nil {
  1089  			return nil, err
  1090  		}
  1091  		cs.Matrix = matrix
  1092  	}
  1093  
  1094  	return cs, nil
  1095  }
  1096  
  1097  // Return as PDF object format [name dictionary]
  1098  func (this *PdfColorspaceCalRGB) ToPdfObject() PdfObject {
  1099  	// CalRGB color space dictionary..
  1100  	cspace := &PdfObjectArray{}
  1101  
  1102  	cspace.Append(MakeName("CalRGB"))
  1103  
  1104  	dict := MakeDict()
  1105  	if this.WhitePoint != nil {
  1106  		wp := MakeArray(MakeFloat(this.WhitePoint[0]), MakeFloat(this.WhitePoint[1]), MakeFloat(this.WhitePoint[2]))
  1107  		dict.Set("WhitePoint", wp)
  1108  	} else {
  1109  		common.Log.Error("CalRGB: Missing WhitePoint (Required)")
  1110  	}
  1111  
  1112  	if this.BlackPoint != nil {
  1113  		bp := MakeArray(MakeFloat(this.BlackPoint[0]), MakeFloat(this.BlackPoint[1]), MakeFloat(this.BlackPoint[2]))
  1114  		dict.Set("BlackPoint", bp)
  1115  	}
  1116  	if this.Gamma != nil {
  1117  		g := MakeArray(MakeFloat(this.Gamma[0]), MakeFloat(this.Gamma[1]), MakeFloat(this.Gamma[2]))
  1118  		dict.Set("Gamma", g)
  1119  	}
  1120  	if this.Matrix != nil {
  1121  		matrix := MakeArray(MakeFloat(this.Matrix[0]), MakeFloat(this.Matrix[1]), MakeFloat(this.Matrix[2]),
  1122  			MakeFloat(this.Matrix[3]), MakeFloat(this.Matrix[4]), MakeFloat(this.Matrix[5]),
  1123  			MakeFloat(this.Matrix[6]), MakeFloat(this.Matrix[7]), MakeFloat(this.Matrix[8]))
  1124  		dict.Set("Matrix", matrix)
  1125  	}
  1126  	cspace.Append(dict)
  1127  
  1128  	if this.container != nil {
  1129  		this.container.PdfObject = cspace
  1130  		return this.container
  1131  	}
  1132  
  1133  	return cspace
  1134  }
  1135  
  1136  func (this *PdfColorspaceCalRGB) ColorFromFloats(vals []float64) (PdfColor, error) {
  1137  	if len(vals) != 3 {
  1138  		return nil, errors.New("Range check")
  1139  	}
  1140  
  1141  	// A
  1142  	a := vals[0]
  1143  	if a < 0.0 || a > 1.0 {
  1144  		return nil, errors.New("Range check")
  1145  	}
  1146  
  1147  	// B
  1148  	b := vals[1]
  1149  	if b < 0.0 || b > 1.0 {
  1150  		return nil, errors.New("Range check")
  1151  	}
  1152  
  1153  	// C.
  1154  	c := vals[2]
  1155  	if c < 0.0 || c > 1.0 {
  1156  		return nil, errors.New("Range check")
  1157  	}
  1158  
  1159  	color := NewPdfColorCalRGB(a, b, c)
  1160  	return color, nil
  1161  }
  1162  
  1163  func (this *PdfColorspaceCalRGB) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) {
  1164  	if len(objects) != 3 {
  1165  		return nil, errors.New("Range check")
  1166  	}
  1167  
  1168  	floats, err := getNumbersAsFloat(objects)
  1169  	if err != nil {
  1170  		return nil, err
  1171  	}
  1172  
  1173  	return this.ColorFromFloats(floats)
  1174  }
  1175  
  1176  func (this *PdfColorspaceCalRGB) ColorToRGB(color PdfColor) (PdfColor, error) {
  1177  	calrgb, ok := color.(*PdfColorCalRGB)
  1178  	if !ok {
  1179  		common.Log.Debug("Input color not cal rgb")
  1180  		return nil, errors.New("Type check error")
  1181  	}
  1182  
  1183  	// A, B, C in range 0.0 to 1.0
  1184  	aVal := calrgb.A()
  1185  	bVal := calrgb.B()
  1186  	cVal := calrgb.C()
  1187  
  1188  	// A, B, C -> X,Y,Z
  1189  	// Gamma [GR GC GB]
  1190  	// Matrix [XA YA ZA XB YB ZB XC YC ZC]
  1191  	X := this.Matrix[0]*math.Pow(aVal, this.Gamma[0]) + this.Matrix[3]*math.Pow(bVal, this.Gamma[1]) + this.Matrix[6]*math.Pow(cVal, this.Gamma[2])
  1192  	Y := this.Matrix[1]*math.Pow(aVal, this.Gamma[0]) + this.Matrix[4]*math.Pow(bVal, this.Gamma[1]) + this.Matrix[7]*math.Pow(cVal, this.Gamma[2])
  1193  	Z := this.Matrix[2]*math.Pow(aVal, this.Gamma[0]) + this.Matrix[5]*math.Pow(bVal, this.Gamma[1]) + this.Matrix[8]*math.Pow(cVal, this.Gamma[2])
  1194  
  1195  	// X, Y, Z -> R, G, B
  1196  	// http://stackoverflow.com/questions/21576719/how-to-convert-cie-color-space-into-rgb-or-hex-color-code-in-php
  1197  	r := 3.240479*X + -1.537150*Y + -0.498535*Z
  1198  	g := -0.969256*X + 1.875992*Y + 0.041556*Z
  1199  	b := 0.055648*X + -0.204043*Y + 1.057311*Z
  1200  
  1201  	// Clip.
  1202  	r = math.Min(math.Max(r, 0), 1.0)
  1203  	g = math.Min(math.Max(g, 0), 1.0)
  1204  	b = math.Min(math.Max(b, 0), 1.0)
  1205  
  1206  	return NewPdfColorDeviceRGB(r, g, b), nil
  1207  }
  1208  
  1209  func (this *PdfColorspaceCalRGB) ImageToRGB(img Image) (Image, error) {
  1210  	rgbImage := img
  1211  
  1212  	samples := img.GetSamples()
  1213  	maxVal := math.Pow(2, float64(img.BitsPerComponent)) - 1
  1214  
  1215  	rgbSamples := []uint32{}
  1216  	for i := 0; i < len(samples)-2; i++ {
  1217  		// A, B, C in range 0.0 to 1.0
  1218  		aVal := float64(samples[i]) / maxVal
  1219  		bVal := float64(samples[i+1]) / maxVal
  1220  		cVal := float64(samples[i+2]) / maxVal
  1221  
  1222  		// A, B, C -> X,Y,Z
  1223  		// Gamma [GR GC GB]
  1224  		// Matrix [XA YA ZA XB YB ZB XC YC ZC]
  1225  		X := this.Matrix[0]*math.Pow(aVal, this.Gamma[0]) + this.Matrix[3]*math.Pow(bVal, this.Gamma[1]) + this.Matrix[6]*math.Pow(cVal, this.Gamma[2])
  1226  		Y := this.Matrix[1]*math.Pow(aVal, this.Gamma[0]) + this.Matrix[4]*math.Pow(bVal, this.Gamma[1]) + this.Matrix[7]*math.Pow(cVal, this.Gamma[2])
  1227  		Z := this.Matrix[2]*math.Pow(aVal, this.Gamma[0]) + this.Matrix[5]*math.Pow(bVal, this.Gamma[1]) + this.Matrix[8]*math.Pow(cVal, this.Gamma[2])
  1228  
  1229  		// X, Y, Z -> R, G, B
  1230  		// http://stackoverflow.com/questions/21576719/how-to-convert-cie-color-space-into-rgb-or-hex-color-code-in-php
  1231  		r := 3.240479*X + -1.537150*Y + -0.498535*Z
  1232  		g := -0.969256*X + 1.875992*Y + 0.041556*Z
  1233  		b := 0.055648*X + -0.204043*Y + 1.057311*Z
  1234  
  1235  		// Clip.
  1236  		r = math.Min(math.Max(r, 0), 1.0)
  1237  		g = math.Min(math.Max(g, 0), 1.0)
  1238  		b = math.Min(math.Max(b, 0), 1.0)
  1239  
  1240  		// Convert to uint32.
  1241  		R := uint32(r * maxVal)
  1242  		G := uint32(g * maxVal)
  1243  		B := uint32(b * maxVal)
  1244  
  1245  		rgbSamples = append(rgbSamples, R, G, B)
  1246  	}
  1247  	rgbImage.SetSamples(rgbSamples)
  1248  	rgbImage.ColorComponents = 3
  1249  
  1250  	return rgbImage, nil
  1251  }
  1252  
  1253  //////////////////////
  1254  // L*, a*, b* 3 component colorspace.
  1255  // Each component is defined in the range 0.0 - 1.0 where 1.0 is the primary intensity.
  1256  
  1257  type PdfColorLab [3]float64
  1258  
  1259  func NewPdfColorLab(l, a, b float64) *PdfColorLab {
  1260  	color := PdfColorLab{l, a, b}
  1261  	return &color
  1262  }
  1263  
  1264  func (this *PdfColorLab) GetNumComponents() int {
  1265  	return 3
  1266  }
  1267  
  1268  func (this *PdfColorLab) L() float64 {
  1269  	return float64(this[0])
  1270  }
  1271  
  1272  func (this *PdfColorLab) A() float64 {
  1273  	return float64(this[1])
  1274  }
  1275  
  1276  func (this *PdfColorLab) B() float64 {
  1277  	return float64(this[2])
  1278  }
  1279  
  1280  // Convert to an integer format.
  1281  func (this *PdfColorLab) ToInteger(bits int) [3]uint32 {
  1282  	maxVal := math.Pow(2, float64(bits)) - 1
  1283  	return [3]uint32{uint32(maxVal * this.L()), uint32(maxVal * this.A()), uint32(maxVal * this.B())}
  1284  }
  1285  
  1286  // L*, a*, b* 3 component colorspace.
  1287  type PdfColorspaceLab struct {
  1288  	WhitePoint []float64 // Required.
  1289  	BlackPoint []float64
  1290  	Range      []float64 // [amin amax bmin bmax]
  1291  
  1292  	container *PdfIndirectObject
  1293  }
  1294  
  1295  func (this *PdfColorspaceLab) String() string {
  1296  	return "Lab"
  1297  }
  1298  
  1299  func (this *PdfColorspaceLab) GetNumComponents() int {
  1300  	return 3
  1301  }
  1302  
  1303  // DecodeArray returns the range of color component values in the Lab colorspace.
  1304  func (this *PdfColorspaceLab) DecodeArray() []float64 {
  1305  	// Range for L
  1306  	decode := []float64{0, 100}
  1307  
  1308  	// Range for A,B specified by range or default
  1309  	if this.Range != nil && len(this.Range) == 4 {
  1310  		decode = append(decode, this.Range...)
  1311  	} else {
  1312  		decode = append(decode, -100, 100, -100, 100)
  1313  	}
  1314  
  1315  	return decode
  1316  }
  1317  
  1318  // require parameters?
  1319  func NewPdfColorspaceLab() *PdfColorspaceLab {
  1320  	cs := &PdfColorspaceLab{}
  1321  
  1322  	// Set optional parameters to default values.
  1323  	cs.BlackPoint = []float64{0.0, 0.0, 0.0}
  1324  	cs.Range = []float64{-100, 100, -100, 100} // Identity matrix.
  1325  
  1326  	return cs
  1327  }
  1328  
  1329  func newPdfColorspaceLabFromPdfObject(obj PdfObject) (*PdfColorspaceLab, error) {
  1330  	cs := NewPdfColorspaceLab()
  1331  
  1332  	if indObj, isIndirect := obj.(*PdfIndirectObject); isIndirect {
  1333  		cs.container = indObj
  1334  	}
  1335  
  1336  	obj = TraceToDirectObject(obj)
  1337  	array, ok := obj.(*PdfObjectArray)
  1338  	if !ok {
  1339  		return nil, fmt.Errorf("Type error")
  1340  	}
  1341  
  1342  	if len(*array) != 2 {
  1343  		return nil, fmt.Errorf("Invalid CalRGB colorspace")
  1344  	}
  1345  
  1346  	// Name.
  1347  	obj = TraceToDirectObject((*array)[0])
  1348  	name, ok := obj.(*PdfObjectName)
  1349  	if !ok {
  1350  		return nil, fmt.Errorf("Lab name not a Name object")
  1351  	}
  1352  	if *name != "Lab" {
  1353  		return nil, fmt.Errorf("Not a Lab colorspace")
  1354  	}
  1355  
  1356  	// Dict.
  1357  	obj = TraceToDirectObject((*array)[1])
  1358  	dict, ok := obj.(*PdfObjectDictionary)
  1359  	if !ok {
  1360  		return nil, fmt.Errorf("Colorspace dictionary missing or invalid")
  1361  	}
  1362  
  1363  	// WhitePoint (Required): [Xw, Yw, Zw]
  1364  	obj = dict.Get("WhitePoint")
  1365  	obj = TraceToDirectObject(obj)
  1366  	whitePointArray, ok := obj.(*PdfObjectArray)
  1367  	if !ok {
  1368  		return nil, fmt.Errorf("Lab Invalid WhitePoint")
  1369  	}
  1370  	if len(*whitePointArray) != 3 {
  1371  		return nil, fmt.Errorf("Lab: Invalid WhitePoint array")
  1372  	}
  1373  	whitePoint, err := whitePointArray.GetAsFloat64Slice()
  1374  	if err != nil {
  1375  		return nil, err
  1376  	}
  1377  	cs.WhitePoint = whitePoint
  1378  
  1379  	// BlackPoint (Optional)
  1380  	obj = dict.Get("BlackPoint")
  1381  	if obj != nil {
  1382  		obj = TraceToDirectObject(obj)
  1383  		blackPointArray, ok := obj.(*PdfObjectArray)
  1384  		if !ok {
  1385  			return nil, fmt.Errorf("Lab: Invalid BlackPoint")
  1386  		}
  1387  		if len(*blackPointArray) != 3 {
  1388  			return nil, fmt.Errorf("Lab: Invalid BlackPoint array")
  1389  		}
  1390  		blackPoint, err := blackPointArray.GetAsFloat64Slice()
  1391  		if err != nil {
  1392  			return nil, err
  1393  		}
  1394  		cs.BlackPoint = blackPoint
  1395  	}
  1396  
  1397  	// Range (Optional)
  1398  	obj = dict.Get("Range")
  1399  	if obj != nil {
  1400  		obj = TraceToDirectObject(obj)
  1401  		rangeArray, ok := obj.(*PdfObjectArray)
  1402  		if !ok {
  1403  			common.Log.Error("Range type error")
  1404  			return nil, fmt.Errorf("Lab: Type error")
  1405  		}
  1406  		if len(*rangeArray) != 4 {
  1407  			common.Log.Error("Range range error")
  1408  			return nil, fmt.Errorf("Lab: Range error")
  1409  		}
  1410  		rang, err := rangeArray.GetAsFloat64Slice()
  1411  		if err != nil {
  1412  			return nil, err
  1413  		}
  1414  		cs.Range = rang
  1415  	}
  1416  
  1417  	return cs, nil
  1418  }
  1419  
  1420  // Return as PDF object format [name dictionary]
  1421  func (this *PdfColorspaceLab) ToPdfObject() PdfObject {
  1422  	// CalRGB color space dictionary..
  1423  	csObj := &PdfObjectArray{}
  1424  
  1425  	csObj.Append(MakeName("Lab"))
  1426  
  1427  	dict := MakeDict()
  1428  	if this.WhitePoint != nil {
  1429  		wp := MakeArray(MakeFloat(this.WhitePoint[0]), MakeFloat(this.WhitePoint[1]), MakeFloat(this.WhitePoint[2]))
  1430  		dict.Set("WhitePoint", wp)
  1431  	} else {
  1432  		common.Log.Error("Lab: Missing WhitePoint (Required)")
  1433  	}
  1434  
  1435  	if this.BlackPoint != nil {
  1436  		bp := MakeArray(MakeFloat(this.BlackPoint[0]), MakeFloat(this.BlackPoint[1]), MakeFloat(this.BlackPoint[2]))
  1437  		dict.Set("BlackPoint", bp)
  1438  	}
  1439  
  1440  	if this.Range != nil {
  1441  		val := MakeArray(MakeFloat(this.Range[0]), MakeFloat(this.Range[1]), MakeFloat(this.Range[2]), MakeFloat(this.Range[3]))
  1442  		dict.Set("Range", val)
  1443  	}
  1444  	csObj.Append(dict)
  1445  
  1446  	if this.container != nil {
  1447  		this.container.PdfObject = csObj
  1448  		return this.container
  1449  	}
  1450  
  1451  	return csObj
  1452  }
  1453  
  1454  func (this *PdfColorspaceLab) ColorFromFloats(vals []float64) (PdfColor, error) {
  1455  	if len(vals) != 3 {
  1456  		return nil, errors.New("Range check")
  1457  	}
  1458  
  1459  	// L
  1460  	l := vals[0]
  1461  	if l < 0.0 || l > 100.0 {
  1462  		common.Log.Debug("L out of range (got %v should be 0-100)", l)
  1463  		return nil, errors.New("Range check")
  1464  	}
  1465  
  1466  	// A
  1467  	a := vals[1]
  1468  	aMin := float64(-100)
  1469  	aMax := float64(100)
  1470  	if len(this.Range) > 1 {
  1471  		aMin = this.Range[0]
  1472  		aMax = this.Range[1]
  1473  	}
  1474  	if a < aMin || a > aMax {
  1475  		common.Log.Debug("A out of range (got %v; range %v to %v)", a, aMin, aMax)
  1476  		return nil, errors.New("Range check")
  1477  	}
  1478  
  1479  	// B.
  1480  	b := vals[2]
  1481  	bMin := float64(-100)
  1482  	bMax := float64(100)
  1483  	if len(this.Range) > 3 {
  1484  		bMin = this.Range[2]
  1485  		bMax = this.Range[3]
  1486  	}
  1487  	if b < bMin || b > bMax {
  1488  		common.Log.Debug("b out of range (got %v; range %v to %v)", b, bMin, bMax)
  1489  		return nil, errors.New("Range check")
  1490  	}
  1491  
  1492  	color := NewPdfColorLab(l, a, b)
  1493  	return color, nil
  1494  }
  1495  
  1496  func (this *PdfColorspaceLab) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) {
  1497  	if len(objects) != 3 {
  1498  		return nil, errors.New("Range check")
  1499  	}
  1500  
  1501  	floats, err := getNumbersAsFloat(objects)
  1502  	if err != nil {
  1503  		return nil, err
  1504  	}
  1505  
  1506  	return this.ColorFromFloats(floats)
  1507  }
  1508  
  1509  func (this *PdfColorspaceLab) ColorToRGB(color PdfColor) (PdfColor, error) {
  1510  	gFunc := func(x float64) float64 {
  1511  		if x >= 6.0/29 {
  1512  			return x * x * x
  1513  		} else {
  1514  			return 108.0 / 841 * (x - 4/29)
  1515  		}
  1516  	}
  1517  
  1518  	lab, ok := color.(*PdfColorLab)
  1519  	if !ok {
  1520  		common.Log.Debug("input color not lab")
  1521  		return nil, errors.New("Type check error")
  1522  	}
  1523  
  1524  	// Get L*, a*, b* values.
  1525  	LStar := lab.L()
  1526  	AStar := lab.A()
  1527  	BStar := lab.B()
  1528  
  1529  	// Convert L*,a*,b* -> L, M, N
  1530  	L := (LStar+16)/116 + AStar/500
  1531  	M := (LStar + 16) / 116
  1532  	N := (LStar+16)/116 - BStar/200
  1533  
  1534  	// L, M, N -> X,Y,Z
  1535  	X := this.WhitePoint[0] * gFunc(L)
  1536  	Y := this.WhitePoint[1] * gFunc(M)
  1537  	Z := this.WhitePoint[2] * gFunc(N)
  1538  
  1539  	// Convert to RGB.
  1540  	// X, Y, Z -> R, G, B
  1541  	// http://stackoverflow.com/questions/21576719/how-to-convert-cie-color-space-into-rgb-or-hex-color-code-in-php
  1542  	r := 3.240479*X + -1.537150*Y + -0.498535*Z
  1543  	g := -0.969256*X + 1.875992*Y + 0.041556*Z
  1544  	b := 0.055648*X + -0.204043*Y + 1.057311*Z
  1545  
  1546  	// Clip.
  1547  	r = math.Min(math.Max(r, 0), 1.0)
  1548  	g = math.Min(math.Max(g, 0), 1.0)
  1549  	b = math.Min(math.Max(b, 0), 1.0)
  1550  
  1551  	return NewPdfColorDeviceRGB(r, g, b), nil
  1552  }
  1553  
  1554  func (this *PdfColorspaceLab) ImageToRGB(img Image) (Image, error) {
  1555  	g := func(x float64) float64 {
  1556  		if x >= 6.0/29 {
  1557  			return x * x * x
  1558  		} else {
  1559  			return 108.0 / 841 * (x - 4/29)
  1560  		}
  1561  	}
  1562  
  1563  	rgbImage := img
  1564  
  1565  	// Each n-bit unit within the bit stream shall be interpreted as an unsigned integer in the range 0 to 2n- 1,
  1566  	// with the high-order bit first.
  1567  	// The image dictionary’s Decode entry maps this integer to a colour component value, equivalent to what could be
  1568  	// used with colour operators such as sc or g.
  1569  
  1570  	componentRanges := img.decode
  1571  	if len(componentRanges) != 6 {
  1572  		// If image's Decode not appropriate, fall back to default decode array.
  1573  		common.Log.Trace("Image - Lab Decode range != 6... use [0 100 amin amax bmin bmax] default decode array")
  1574  		componentRanges = this.DecodeArray()
  1575  	}
  1576  
  1577  	samples := img.GetSamples()
  1578  	maxVal := math.Pow(2, float64(img.BitsPerComponent)) - 1
  1579  
  1580  	rgbSamples := []uint32{}
  1581  	for i := 0; i < len(samples); i += 3 {
  1582  		// Get normalized L*, a*, b* values. [0-1]
  1583  		LNorm := float64(samples[i]) / maxVal
  1584  		ANorm := float64(samples[i+1]) / maxVal
  1585  		BNorm := float64(samples[i+2]) / maxVal
  1586  
  1587  		LStar := interpolate(LNorm, 0.0, 1.0, componentRanges[0], componentRanges[1])
  1588  		AStar := interpolate(ANorm, 0.0, 1.0, componentRanges[2], componentRanges[3])
  1589  		BStar := interpolate(BNorm, 0.0, 1.0, componentRanges[4], componentRanges[5])
  1590  
  1591  		// Convert L*,a*,b* -> L, M, N
  1592  		L := (LStar+16)/116 + AStar/500
  1593  		M := (LStar + 16) / 116
  1594  		N := (LStar+16)/116 - BStar/200
  1595  
  1596  		// L, M, N -> X,Y,Z
  1597  		X := this.WhitePoint[0] * g(L)
  1598  		Y := this.WhitePoint[1] * g(M)
  1599  		Z := this.WhitePoint[2] * g(N)
  1600  
  1601  		// Convert to RGB.
  1602  		// X, Y, Z -> R, G, B
  1603  		// http://stackoverflow.com/questions/21576719/how-to-convert-cie-color-space-into-rgb-or-hex-color-code-in-php
  1604  		r := 3.240479*X + -1.537150*Y + -0.498535*Z
  1605  		g := -0.969256*X + 1.875992*Y + 0.041556*Z
  1606  		b := 0.055648*X + -0.204043*Y + 1.057311*Z
  1607  
  1608  		// Clip.
  1609  		r = math.Min(math.Max(r, 0), 1.0)
  1610  		g = math.Min(math.Max(g, 0), 1.0)
  1611  		b = math.Min(math.Max(b, 0), 1.0)
  1612  
  1613  		// Convert to uint32.
  1614  		R := uint32(r * maxVal)
  1615  		G := uint32(g * maxVal)
  1616  		B := uint32(b * maxVal)
  1617  
  1618  		rgbSamples = append(rgbSamples, R, G, B)
  1619  	}
  1620  	rgbImage.SetSamples(rgbSamples)
  1621  	rgbImage.ColorComponents = 3
  1622  
  1623  	return rgbImage, nil
  1624  }
  1625  
  1626  //////////////////////
  1627  // ICC Based colors.
  1628  // Each component is defined in the range 0.0 - 1.0 where 1.0 is the primary intensity.
  1629  
  1630  /*
  1631  type PdfColorICCBased []float64
  1632  
  1633  func NewPdfColorICCBased(vals []float64) *PdfColorICCBased {
  1634  	color := PdfColorICCBased{}
  1635  	for _, val := range vals {
  1636  		color = append(color, val)
  1637  	}
  1638  	return &color
  1639  }
  1640  
  1641  func (this *PdfColorICCBased) GetNumComponents() int {
  1642  	return len(*this)
  1643  }
  1644  
  1645  // Convert to an integer format.
  1646  func (this *PdfColorICCBased) ToInteger(bits int) []uint32 {
  1647  	maxVal := math.Pow(2, float64(bits)) - 1
  1648  	ints := []uint32{}
  1649  	for _, val := range *this {
  1650  		ints = append(ints, uint32(maxVal*val))
  1651  	}
  1652  
  1653  	return ints
  1654  
  1655  }
  1656  */
  1657  // See p. 157 for calculations...
  1658  
  1659  // format [/ICCBased stream]
  1660  //
  1661  // The stream shall contain the ICC profile.
  1662  // A conforming reader shall support ICC.1:2004:10 as required by PDF 1.7, which will enable it
  1663  // to properly render all embedded ICC profiles regardless of the PDF version
  1664  //
  1665  // In the current implementation, we rely on the alternative colormap provided.
  1666  type PdfColorspaceICCBased struct {
  1667  	N         int           // Number of color components (Required). Can be 1,3, or 4.
  1668  	Alternate PdfColorspace // Alternate colorspace for non-conforming readers.
  1669  	// If omitted ICC not supported: then use DeviceGray,
  1670  	// DeviceRGB or DeviceCMYK for N=1,3,4 respectively.
  1671  	Range    []float64        // Array of 2xN numbers, specifying range of each color component.
  1672  	Metadata *PdfObjectStream // Metadata stream.
  1673  	Data     []byte           // ICC colormap data.
  1674  
  1675  	container *PdfIndirectObject
  1676  	stream    *PdfObjectStream
  1677  }
  1678  
  1679  func (this *PdfColorspaceICCBased) GetNumComponents() int {
  1680  	return this.N
  1681  }
  1682  
  1683  // DecodeArray returns the range of color component values in the ICCBased colorspace.
  1684  func (this *PdfColorspaceICCBased) DecodeArray() []float64 {
  1685  	return this.Range
  1686  }
  1687  
  1688  func (this *PdfColorspaceICCBased) String() string {
  1689  	return "ICCBased"
  1690  }
  1691  
  1692  func NewPdfColorspaceICCBased(N int) (*PdfColorspaceICCBased, error) {
  1693  	cs := &PdfColorspaceICCBased{}
  1694  
  1695  	if N != 1 && N != 3 && N != 4 {
  1696  		return nil, fmt.Errorf("Invalid N (1/3/4)")
  1697  	}
  1698  
  1699  	cs.N = N
  1700  
  1701  	return cs, nil
  1702  }
  1703  
  1704  // Input format [/ICCBased stream]
  1705  func newPdfColorspaceICCBasedFromPdfObject(obj PdfObject) (*PdfColorspaceICCBased, error) {
  1706  	cs := &PdfColorspaceICCBased{}
  1707  	if indObj, isIndirect := obj.(*PdfIndirectObject); isIndirect {
  1708  		cs.container = indObj
  1709  	}
  1710  
  1711  	obj = TraceToDirectObject(obj)
  1712  	array, ok := obj.(*PdfObjectArray)
  1713  	if !ok {
  1714  		return nil, fmt.Errorf("Type error")
  1715  	}
  1716  
  1717  	if len(*array) != 2 {
  1718  		return nil, fmt.Errorf("Invalid ICCBased colorspace")
  1719  	}
  1720  
  1721  	// Name.
  1722  	obj = TraceToDirectObject((*array)[0])
  1723  	name, ok := obj.(*PdfObjectName)
  1724  	if !ok {
  1725  		return nil, fmt.Errorf("ICCBased name not a Name object")
  1726  	}
  1727  	if *name != "ICCBased" {
  1728  		return nil, fmt.Errorf("Not an ICCBased colorspace")
  1729  	}
  1730  
  1731  	// Stream
  1732  	obj = (*array)[1]
  1733  	stream, ok := obj.(*PdfObjectStream)
  1734  	if !ok {
  1735  		common.Log.Error("ICCBased not pointing to stream: %T", obj)
  1736  		return nil, fmt.Errorf("ICCBased stream invalid")
  1737  	}
  1738  
  1739  	dict := stream.PdfObjectDictionary
  1740  
  1741  	n, ok := dict.Get("N").(*PdfObjectInteger)
  1742  	if !ok {
  1743  		return nil, fmt.Errorf("ICCBased missing N from stream dict")
  1744  	}
  1745  	if *n != 1 && *n != 3 && *n != 4 {
  1746  		return nil, fmt.Errorf("ICCBased colorspace invalid N (not 1,3,4)")
  1747  	}
  1748  	cs.N = int(*n)
  1749  
  1750  	if obj := dict.Get("Alternate"); obj != nil {
  1751  		alternate, err := NewPdfColorspaceFromPdfObject(obj)
  1752  		if err != nil {
  1753  			return nil, err
  1754  		}
  1755  		cs.Alternate = alternate
  1756  	}
  1757  
  1758  	if obj := dict.Get("Range"); obj != nil {
  1759  		obj = TraceToDirectObject(obj)
  1760  		array, ok := obj.(*PdfObjectArray)
  1761  		if !ok {
  1762  			return nil, fmt.Errorf("ICCBased Range not an array")
  1763  		}
  1764  		if len(*array) != 2*cs.N {
  1765  			return nil, fmt.Errorf("ICCBased Range wrong number of elements")
  1766  		}
  1767  		r, err := array.GetAsFloat64Slice()
  1768  		if err != nil {
  1769  			return nil, err
  1770  		}
  1771  		cs.Range = r
  1772  	}
  1773  
  1774  	if obj := dict.Get("Metadata"); obj != nil {
  1775  		stream, ok := obj.(*PdfObjectStream)
  1776  		if !ok {
  1777  			return nil, fmt.Errorf("ICCBased Metadata not a stream")
  1778  		}
  1779  		cs.Metadata = stream
  1780  	}
  1781  
  1782  	data, err := DecodeStream(stream)
  1783  	if err != nil {
  1784  		return nil, err
  1785  	}
  1786  	cs.Data = data
  1787  	cs.stream = stream
  1788  
  1789  	return cs, nil
  1790  }
  1791  
  1792  // Return as PDF object format [name stream]
  1793  func (this *PdfColorspaceICCBased) ToPdfObject() PdfObject {
  1794  	csObj := &PdfObjectArray{}
  1795  
  1796  	csObj.Append(MakeName("ICCBased"))
  1797  
  1798  	var stream *PdfObjectStream
  1799  	if this.stream != nil {
  1800  		stream = this.stream
  1801  	} else {
  1802  		stream = &PdfObjectStream{}
  1803  	}
  1804  	dict := MakeDict()
  1805  
  1806  	dict.Set("N", MakeInteger(int64(this.N)))
  1807  
  1808  	if this.Alternate != nil {
  1809  		dict.Set("Alternate", this.Alternate.ToPdfObject())
  1810  	}
  1811  
  1812  	if this.Metadata != nil {
  1813  		dict.Set("Metadata", this.Metadata)
  1814  	}
  1815  	if this.Range != nil {
  1816  		ranges := []PdfObject{}
  1817  		for _, r := range this.Range {
  1818  			ranges = append(ranges, MakeFloat(r))
  1819  		}
  1820  		dict.Set("Range", MakeArray(ranges...))
  1821  	}
  1822  
  1823  	// Encode with a default encoder?
  1824  	dict.Set("Length", MakeInteger(int64(len(this.Data))))
  1825  	// Need to have a representation of the stream...
  1826  	stream.Stream = this.Data
  1827  	stream.PdfObjectDictionary = dict
  1828  
  1829  	csObj.Append(stream)
  1830  
  1831  	if this.container != nil {
  1832  		this.container.PdfObject = csObj
  1833  		return this.container
  1834  	}
  1835  
  1836  	return csObj
  1837  }
  1838  
  1839  func (this *PdfColorspaceICCBased) ColorFromFloats(vals []float64) (PdfColor, error) {
  1840  	if this.Alternate == nil {
  1841  		if this.N == 1 {
  1842  			cs := NewPdfColorspaceDeviceGray()
  1843  			return cs.ColorFromFloats(vals)
  1844  		} else if this.N == 3 {
  1845  			cs := NewPdfColorspaceDeviceRGB()
  1846  			return cs.ColorFromFloats(vals)
  1847  		} else if this.N == 4 {
  1848  			cs := NewPdfColorspaceDeviceCMYK()
  1849  			return cs.ColorFromFloats(vals)
  1850  		} else {
  1851  			return nil, errors.New("ICC Based colorspace missing alternative")
  1852  		}
  1853  	}
  1854  
  1855  	return this.Alternate.ColorFromFloats(vals)
  1856  }
  1857  
  1858  func (this *PdfColorspaceICCBased) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) {
  1859  	if this.Alternate == nil {
  1860  		if this.N == 1 {
  1861  			cs := NewPdfColorspaceDeviceGray()
  1862  			return cs.ColorFromPdfObjects(objects)
  1863  		} else if this.N == 3 {
  1864  			cs := NewPdfColorspaceDeviceRGB()
  1865  			return cs.ColorFromPdfObjects(objects)
  1866  		} else if this.N == 4 {
  1867  			cs := NewPdfColorspaceDeviceCMYK()
  1868  			return cs.ColorFromPdfObjects(objects)
  1869  		} else {
  1870  			return nil, errors.New("ICC Based colorspace missing alternative")
  1871  		}
  1872  	}
  1873  
  1874  	return this.Alternate.ColorFromPdfObjects(objects)
  1875  }
  1876  
  1877  func (this *PdfColorspaceICCBased) ColorToRGB(color PdfColor) (PdfColor, error) {
  1878  	/*
  1879  		_, ok := color.(*PdfColorICCBased)
  1880  		if !ok {
  1881  			common.Log.Debug("ICC Based color error, type: %T", color)
  1882  			return nil, errors.New("Type check error")
  1883  		}
  1884  	*/
  1885  
  1886  	if this.Alternate == nil {
  1887  		common.Log.Debug("ICC Based colorspace missing alternative")
  1888  		if this.N == 1 {
  1889  			common.Log.Debug("ICC Based colorspace missing alternative - using DeviceGray (N=1)")
  1890  			grayCS := NewPdfColorspaceDeviceGray()
  1891  			return grayCS.ColorToRGB(color)
  1892  		} else if this.N == 3 {
  1893  			common.Log.Debug("ICC Based colorspace missing alternative - using DeviceRGB (N=3)")
  1894  			// Already in RGB.
  1895  			return color, nil
  1896  		} else if this.N == 4 {
  1897  			common.Log.Debug("ICC Based colorspace missing alternative - using DeviceCMYK (N=4)")
  1898  			// CMYK
  1899  			cmykCS := NewPdfColorspaceDeviceCMYK()
  1900  			return cmykCS.ColorToRGB(color)
  1901  		} else {
  1902  			return nil, errors.New("ICC Based colorspace missing alternative")
  1903  		}
  1904  	}
  1905  
  1906  	common.Log.Trace("ICC Based colorspace with alternative: %#v", this)
  1907  	return this.Alternate.ColorToRGB(color)
  1908  }
  1909  
  1910  func (this *PdfColorspaceICCBased) ImageToRGB(img Image) (Image, error) {
  1911  	if this.Alternate == nil {
  1912  		common.Log.Debug("ICC Based colorspace missing alternative")
  1913  		if this.N == 1 {
  1914  			common.Log.Debug("ICC Based colorspace missing alternative - using DeviceGray (N=1)")
  1915  			grayCS := NewPdfColorspaceDeviceGray()
  1916  			return grayCS.ImageToRGB(img)
  1917  		} else if this.N == 3 {
  1918  			common.Log.Debug("ICC Based colorspace missing alternative - using DeviceRGB (N=3)")
  1919  			// Already in RGB.
  1920  			return img, nil
  1921  		} else if this.N == 4 {
  1922  			common.Log.Debug("ICC Based colorspace missing alternative - using DeviceCMYK (N=4)")
  1923  			// CMYK
  1924  			cmykCS := NewPdfColorspaceDeviceCMYK()
  1925  			return cmykCS.ImageToRGB(img)
  1926  		} else {
  1927  			return img, errors.New("ICC Based colorspace missing alternative")
  1928  		}
  1929  	}
  1930  	common.Log.Trace("ICC Based colorspace with alternative: %#v", this)
  1931  
  1932  	output, err := this.Alternate.ImageToRGB(img)
  1933  	common.Log.Trace("ICC Input image: %+v", img)
  1934  	common.Log.Trace("ICC Output image: %+v", output)
  1935  	return output, err //this.Alternate.ImageToRGB(img)
  1936  }
  1937  
  1938  //////////////////////
  1939  // Pattern color.
  1940  
  1941  type PdfColorPattern struct {
  1942  	Color       PdfColor      // Color defined in underlying colorspace.
  1943  	PatternName PdfObjectName // Name of the pattern (reference via resource dicts).
  1944  }
  1945  
  1946  // Pattern colorspace.
  1947  // Can be defined either as /Pattern or with an underlying colorspace [/Pattern cs].
  1948  type PdfColorspaceSpecialPattern struct {
  1949  	UnderlyingCS PdfColorspace
  1950  
  1951  	container *PdfIndirectObject
  1952  }
  1953  
  1954  func NewPdfColorspaceSpecialPattern() *PdfColorspaceSpecialPattern {
  1955  	return &PdfColorspaceSpecialPattern{}
  1956  }
  1957  
  1958  func (this *PdfColorspaceSpecialPattern) String() string {
  1959  	return "Pattern"
  1960  }
  1961  
  1962  func (this *PdfColorspaceSpecialPattern) GetNumComponents() int {
  1963  	return this.UnderlyingCS.GetNumComponents()
  1964  }
  1965  
  1966  // DecodeArray returns an empty slice as there are no components associated with pattern colorspace.
  1967  func (this *PdfColorspaceSpecialPattern) DecodeArray() []float64 {
  1968  	return []float64{}
  1969  }
  1970  
  1971  func newPdfColorspaceSpecialPatternFromPdfObject(obj PdfObject) (*PdfColorspaceSpecialPattern, error) {
  1972  	common.Log.Trace("New Pattern CS from obj: %s %T", obj.String(), obj)
  1973  	cs := NewPdfColorspaceSpecialPattern()
  1974  
  1975  	if indObj, isIndirect := obj.(*PdfIndirectObject); isIndirect {
  1976  		cs.container = indObj
  1977  	}
  1978  
  1979  	obj = TraceToDirectObject(obj)
  1980  	if name, isName := obj.(*PdfObjectName); isName {
  1981  		if *name != "Pattern" {
  1982  			return nil, fmt.Errorf("Invalid name")
  1983  		}
  1984  
  1985  		return cs, nil
  1986  	}
  1987  
  1988  	array, ok := obj.(*PdfObjectArray)
  1989  	if !ok {
  1990  		common.Log.Error("Invalid Pattern CS Object: %#v", obj)
  1991  		return nil, fmt.Errorf("Invalid Pattern CS object")
  1992  	}
  1993  	if len(*array) != 1 && len(*array) != 2 {
  1994  		common.Log.Error("Invalid Pattern CS array: %#v", array)
  1995  		return nil, fmt.Errorf("Invalid Pattern CS array")
  1996  	}
  1997  
  1998  	obj = (*array)[0]
  1999  	if name, isName := obj.(*PdfObjectName); isName {
  2000  		if *name != "Pattern" {
  2001  			common.Log.Error("Invalid Pattern CS array name: %#v", name)
  2002  			return nil, fmt.Errorf("Invalid name")
  2003  		}
  2004  	}
  2005  
  2006  	// Has an underlying color space.
  2007  	if len(*array) > 1 {
  2008  		obj = (*array)[1]
  2009  		obj = TraceToDirectObject(obj)
  2010  		baseCS, err := NewPdfColorspaceFromPdfObject(obj)
  2011  		if err != nil {
  2012  			return nil, err
  2013  		}
  2014  		cs.UnderlyingCS = baseCS
  2015  	}
  2016  
  2017  	common.Log.Trace("Returning Pattern with underlying cs: %T", cs.UnderlyingCS)
  2018  	return cs, nil
  2019  }
  2020  
  2021  func (this *PdfColorspaceSpecialPattern) ToPdfObject() PdfObject {
  2022  	if this.UnderlyingCS == nil {
  2023  		return MakeName("Pattern")
  2024  	}
  2025  
  2026  	csObj := MakeArray(MakeName("Pattern"))
  2027  	csObj.Append(this.UnderlyingCS.ToPdfObject())
  2028  
  2029  	if this.container != nil {
  2030  		this.container.PdfObject = csObj
  2031  		return this.container
  2032  	}
  2033  
  2034  	return csObj
  2035  }
  2036  
  2037  func (this *PdfColorspaceSpecialPattern) ColorFromFloats(vals []float64) (PdfColor, error) {
  2038  	if this.UnderlyingCS == nil {
  2039  		return nil, errors.New("Underlying CS not specified")
  2040  	}
  2041  	return this.UnderlyingCS.ColorFromFloats(vals)
  2042  }
  2043  
  2044  // The first objects (if present) represent the color in underlying colorspace.  The last one represents
  2045  // the name of the pattern.
  2046  func (this *PdfColorspaceSpecialPattern) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) {
  2047  	if len(objects) < 1 {
  2048  		return nil, errors.New("Invalid number of parameters")
  2049  	}
  2050  	patternColor := &PdfColorPattern{}
  2051  
  2052  	// Pattern name.
  2053  	pname, ok := objects[len(objects)-1].(*PdfObjectName)
  2054  	if !ok {
  2055  		common.Log.Debug("Pattern name not a name (got %T)", objects[len(objects)-1])
  2056  		return nil, ErrTypeError
  2057  	}
  2058  	patternColor.PatternName = *pname
  2059  
  2060  	// Pattern color if specified.
  2061  	if len(objects) > 1 {
  2062  		colorObjs := objects[0 : len(objects)-1]
  2063  		if this.UnderlyingCS == nil {
  2064  			common.Log.Debug("Pattern color with defined color components but underlying cs missing")
  2065  			return nil, errors.New("Underlying CS not defined")
  2066  		}
  2067  		color, err := this.UnderlyingCS.ColorFromPdfObjects(colorObjs)
  2068  		if err != nil {
  2069  			common.Log.Debug("ERROR: Unable to convert color via underlying cs: %v", err)
  2070  			return nil, err
  2071  		}
  2072  		patternColor.Color = color
  2073  	}
  2074  
  2075  	return patternColor, nil
  2076  }
  2077  
  2078  // Only converts color used with uncolored patterns (defined in underlying colorspace).  Does not go into the
  2079  // pattern objects and convert those.  If that is desired, needs to be done separately.  See for example
  2080  // grayscale conversion example in unidoc-examples repo.
  2081  func (this *PdfColorspaceSpecialPattern) ColorToRGB(color PdfColor) (PdfColor, error) {
  2082  	patternColor, ok := color.(*PdfColorPattern)
  2083  	if !ok {
  2084  		common.Log.Debug("Color not pattern (got %T)", color)
  2085  		return nil, ErrTypeError
  2086  	}
  2087  
  2088  	if patternColor.Color == nil {
  2089  		// No color defined, can return same back.  No transform needed.
  2090  		return color, nil
  2091  	}
  2092  
  2093  	if this.UnderlyingCS == nil {
  2094  		return nil, errors.New("Underlying CS not defined.")
  2095  	}
  2096  
  2097  	return this.UnderlyingCS.ColorToRGB(patternColor.Color)
  2098  }
  2099  
  2100  // An image cannot be defined in a pattern colorspace, returns an error.
  2101  func (this *PdfColorspaceSpecialPattern) ImageToRGB(img Image) (Image, error) {
  2102  	common.Log.Debug("Error: Image cannot be specified in Pattern colorspace")
  2103  	return img, errors.New("Invalid colorspace for image (pattern)")
  2104  }
  2105  
  2106  //////////////////////
  2107  // Indexed colorspace. An indexed color space is a lookup table, where the input element is an index to the lookup
  2108  // table and the output is a color defined in the lookup table in the Base colorspace.
  2109  // [/Indexed base hival lookup]
  2110  type PdfColorspaceSpecialIndexed struct {
  2111  	Base   PdfColorspace
  2112  	HiVal  int
  2113  	Lookup PdfObject
  2114  
  2115  	colorLookup []byte // m*(hival+1); m is number of components in Base colorspace
  2116  
  2117  	container *PdfIndirectObject
  2118  }
  2119  
  2120  func NewPdfColorspaceSpecialIndexed() *PdfColorspaceSpecialIndexed {
  2121  	cs := &PdfColorspaceSpecialIndexed{}
  2122  	cs.HiVal = 255
  2123  	return cs
  2124  }
  2125  
  2126  func (this *PdfColorspaceSpecialIndexed) String() string {
  2127  	return "Indexed"
  2128  }
  2129  
  2130  func (this *PdfColorspaceSpecialIndexed) GetNumComponents() int {
  2131  	return 1
  2132  }
  2133  
  2134  // DecodeArray returns the component range values for the Indexed colorspace.
  2135  func (this *PdfColorspaceSpecialIndexed) DecodeArray() []float64 {
  2136  	return []float64{0, float64(this.HiVal)}
  2137  }
  2138  
  2139  func newPdfColorspaceSpecialIndexedFromPdfObject(obj PdfObject) (*PdfColorspaceSpecialIndexed, error) {
  2140  	cs := NewPdfColorspaceSpecialIndexed()
  2141  
  2142  	if indObj, isIndirect := obj.(*PdfIndirectObject); isIndirect {
  2143  		cs.container = indObj
  2144  	}
  2145  
  2146  	obj = TraceToDirectObject(obj)
  2147  	array, ok := obj.(*PdfObjectArray)
  2148  	if !ok {
  2149  		return nil, fmt.Errorf("Type error")
  2150  	}
  2151  
  2152  	if len(*array) != 4 {
  2153  		return nil, fmt.Errorf("Indexed CS: invalid array length")
  2154  	}
  2155  
  2156  	// Check name.
  2157  	obj = (*array)[0]
  2158  	name, ok := obj.(*PdfObjectName)
  2159  	if !ok {
  2160  		return nil, fmt.Errorf("Indexed CS: invalid name")
  2161  	}
  2162  	if *name != "Indexed" {
  2163  		return nil, fmt.Errorf("Indexed CS: wrong name")
  2164  	}
  2165  
  2166  	// Get base colormap.
  2167  	obj = (*array)[1]
  2168  
  2169  	// Base cs cannot be another /Indexed or /Pattern space.
  2170  	baseName, err := determineColorspaceNameFromPdfObject(obj)
  2171  	if baseName == "Indexed" || baseName == "Pattern" {
  2172  		common.Log.Debug("Error: Indexed colorspace cannot have Indexed/Pattern CS as base (%v)", baseName)
  2173  		return nil, ErrRangeError
  2174  	}
  2175  
  2176  	baseCs, err := NewPdfColorspaceFromPdfObject(obj)
  2177  	if err != nil {
  2178  		return nil, err
  2179  	}
  2180  	cs.Base = baseCs
  2181  
  2182  	// Get hi val.
  2183  	obj = (*array)[2]
  2184  	val, err := getNumberAsInt64(obj)
  2185  	if err != nil {
  2186  		return nil, err
  2187  	}
  2188  	if val > 255 {
  2189  		return nil, fmt.Errorf("Indexed CS: Invalid hival")
  2190  	}
  2191  	cs.HiVal = int(val)
  2192  
  2193  	// Index table.
  2194  	obj = (*array)[3]
  2195  	cs.Lookup = obj
  2196  	obj = TraceToDirectObject(obj)
  2197  	var data []byte
  2198  	if str, ok := obj.(*PdfObjectString); ok {
  2199  		data = []byte(*str)
  2200  		common.Log.Trace("Indexed string color data: % d", data)
  2201  	} else if stream, ok := obj.(*PdfObjectStream); ok {
  2202  		common.Log.Trace("Indexed stream: %s", obj.String())
  2203  		common.Log.Trace("Encoded (%d) : %# x", len(stream.Stream), stream.Stream)
  2204  		decoded, err := DecodeStream(stream)
  2205  		if err != nil {
  2206  			return nil, err
  2207  		}
  2208  		common.Log.Trace("Decoded (%d) : % X", len(decoded), decoded)
  2209  		data = decoded
  2210  	} else {
  2211  		return nil, fmt.Errorf("Indexed CS: Invalid table format")
  2212  	}
  2213  
  2214  	if len(data) < cs.Base.GetNumComponents()*(cs.HiVal+1) {
  2215  		// Sometimes the table length is too short.  In this case we need to
  2216  		// note what absolute maximum index is.
  2217  		common.Log.Debug("PDF Incompatibility: Index stream too short")
  2218  		common.Log.Debug("Fail, len(data): %d, components: %d, hiVal: %d", len(data), cs.Base.GetNumComponents(), cs.HiVal)
  2219  	} else {
  2220  		// trim
  2221  		data = data[:cs.Base.GetNumComponents()*(cs.HiVal+1)]
  2222  	}
  2223  
  2224  	cs.colorLookup = data
  2225  
  2226  	return cs, nil
  2227  }
  2228  
  2229  func (this *PdfColorspaceSpecialIndexed) ColorFromFloats(vals []float64) (PdfColor, error) {
  2230  	if len(vals) != 1 {
  2231  		return nil, errors.New("Range check")
  2232  	}
  2233  
  2234  	N := this.Base.GetNumComponents()
  2235  
  2236  	index := int(vals[0]) * N
  2237  	if index < 0 || (index+N-1) >= len(this.colorLookup) {
  2238  		return nil, errors.New("Outside range")
  2239  	}
  2240  
  2241  	cvals := this.colorLookup[index : index+N]
  2242  	floats := []float64{}
  2243  	for _, val := range cvals {
  2244  		floats = append(floats, float64(val)/255.0)
  2245  	}
  2246  	color, err := this.Base.ColorFromFloats(floats)
  2247  	if err != nil {
  2248  		return nil, err
  2249  	}
  2250  
  2251  	return color, nil
  2252  }
  2253  
  2254  func (this *PdfColorspaceSpecialIndexed) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) {
  2255  	if len(objects) != 1 {
  2256  		return nil, errors.New("Range check")
  2257  	}
  2258  
  2259  	floats, err := getNumbersAsFloat(objects)
  2260  	if err != nil {
  2261  		return nil, err
  2262  	}
  2263  
  2264  	return this.ColorFromFloats(floats)
  2265  }
  2266  
  2267  func (this *PdfColorspaceSpecialIndexed) ColorToRGB(color PdfColor) (PdfColor, error) {
  2268  	if this.Base == nil {
  2269  		return nil, errors.New("Indexed base colorspace undefined")
  2270  	}
  2271  
  2272  	return this.Base.ColorToRGB(color)
  2273  }
  2274  
  2275  // Convert an indexed image to RGB.
  2276  func (this *PdfColorspaceSpecialIndexed) ImageToRGB(img Image) (Image, error) {
  2277  	//baseImage := img
  2278  	// Make a new representation of the image to be converted with the base colorspace.
  2279  	baseImage := Image{}
  2280  	baseImage.Height = img.Height
  2281  	baseImage.Width = img.Width
  2282  	baseImage.alphaData = img.alphaData
  2283  	baseImage.BitsPerComponent = img.BitsPerComponent
  2284  	baseImage.hasAlpha = img.hasAlpha
  2285  	baseImage.ColorComponents = img.ColorComponents
  2286  
  2287  	samples := img.GetSamples()
  2288  	N := this.Base.GetNumComponents()
  2289  
  2290  	baseSamples := []uint32{}
  2291  	// Convert the indexed data to base color map data.
  2292  	for i := 0; i < len(samples); i++ {
  2293  		// Each data point represents an index location.
  2294  		// For each entry there are N values.
  2295  		index := int(samples[i]) * N
  2296  		common.Log.Trace("Indexed Index: %d", index)
  2297  		// Ensure does not go out of bounds.
  2298  		if index+N-1 >= len(this.colorLookup) {
  2299  			// Clip to the end value.
  2300  			index = len(this.colorLookup) - N - 1
  2301  			common.Log.Trace("Clipping to index: %d", index)
  2302  		}
  2303  
  2304  		cvals := this.colorLookup[index : index+N]
  2305  		common.Log.Trace("C Vals: % d", cvals)
  2306  		for _, val := range cvals {
  2307  			baseSamples = append(baseSamples, uint32(val))
  2308  		}
  2309  	}
  2310  	baseImage.SetSamples(baseSamples)
  2311  	baseImage.ColorComponents = N
  2312  
  2313  	common.Log.Trace("Input samples: %d", samples)
  2314  	common.Log.Trace("-> Output samples: %d", baseSamples)
  2315  
  2316  	// Convert to rgb.
  2317  	return this.Base.ImageToRGB(baseImage)
  2318  }
  2319  
  2320  // [/Indexed base hival lookup]
  2321  func (this *PdfColorspaceSpecialIndexed) ToPdfObject() PdfObject {
  2322  	csObj := MakeArray(MakeName("Indexed"))
  2323  	csObj.Append(this.Base.ToPdfObject())
  2324  	csObj.Append(MakeInteger(int64(this.HiVal)))
  2325  	csObj.Append(this.Lookup)
  2326  
  2327  	if this.container != nil {
  2328  		this.container.PdfObject = csObj
  2329  		return this.container
  2330  	}
  2331  
  2332  	return csObj
  2333  }
  2334  
  2335  //////////////////////
  2336  // Separation colorspace.
  2337  // At the moment the colour space is set to a Separation space, the conforming reader shall determine whether the
  2338  // device has an available colorant (e.g. dye) corresponding to the name of the requested space. If so, the conforming
  2339  // reader shall ignore the alternateSpace and tintTransform parameters; subsequent painting operations within the
  2340  // space shall apply the designated colorant directly, according to the tint values supplied.
  2341  //
  2342  // Format: [/Separation name alternateSpace tintTransform]
  2343  type PdfColorspaceSpecialSeparation struct {
  2344  	ColorantName   *PdfObjectName
  2345  	AlternateSpace PdfColorspace
  2346  	TintTransform  PdfFunction
  2347  
  2348  	// Container, if when parsing CS array is inside a container.
  2349  	container *PdfIndirectObject
  2350  }
  2351  
  2352  func NewPdfColorspaceSpecialSeparation() *PdfColorspaceSpecialSeparation {
  2353  	cs := &PdfColorspaceSpecialSeparation{}
  2354  	return cs
  2355  }
  2356  
  2357  func (this *PdfColorspaceSpecialSeparation) String() string {
  2358  	return "Separation"
  2359  }
  2360  
  2361  func (this *PdfColorspaceSpecialSeparation) GetNumComponents() int {
  2362  	return 1
  2363  }
  2364  
  2365  // DecodeArray returns the component range values for the Separation colorspace.
  2366  func (this *PdfColorspaceSpecialSeparation) DecodeArray() []float64 {
  2367  	return []float64{0, 1.0}
  2368  }
  2369  
  2370  // Object is an array or indirect object containing the array.
  2371  func newPdfColorspaceSpecialSeparationFromPdfObject(obj PdfObject) (*PdfColorspaceSpecialSeparation, error) {
  2372  	cs := NewPdfColorspaceSpecialSeparation()
  2373  
  2374  	// If within an indirect object, then make a note of it.  If we write out the PdfObject later
  2375  	// we can reference the same container.  Otherwise is not within a container, but rather
  2376  	// a new array.
  2377  	if indObj, isIndirect := obj.(*PdfIndirectObject); isIndirect {
  2378  		cs.container = indObj
  2379  	}
  2380  
  2381  	obj = TraceToDirectObject(obj)
  2382  	array, ok := obj.(*PdfObjectArray)
  2383  	if !ok {
  2384  		return nil, fmt.Errorf("Separation CS: Invalid object")
  2385  	}
  2386  
  2387  	if len(*array) != 4 {
  2388  		return nil, fmt.Errorf("Separation CS: Incorrect array length")
  2389  	}
  2390  
  2391  	// Check name.
  2392  	obj = (*array)[0]
  2393  	name, ok := obj.(*PdfObjectName)
  2394  	if !ok {
  2395  		return nil, fmt.Errorf("Separation CS: invalid family name")
  2396  	}
  2397  	if *name != "Separation" {
  2398  		return nil, fmt.Errorf("Separation CS: wrong family name")
  2399  	}
  2400  
  2401  	// Get colorant name.
  2402  	obj = (*array)[1]
  2403  	name, ok = obj.(*PdfObjectName)
  2404  	if !ok {
  2405  		return nil, fmt.Errorf("Separation CS: Invalid colorant name")
  2406  	}
  2407  	cs.ColorantName = name
  2408  
  2409  	// Get base colormap.
  2410  	obj = (*array)[2]
  2411  	alternativeCs, err := NewPdfColorspaceFromPdfObject(obj)
  2412  	if err != nil {
  2413  		return nil, err
  2414  	}
  2415  	cs.AlternateSpace = alternativeCs
  2416  
  2417  	// Tint transform is specified by a PDF function.
  2418  	tintTransform, err := newPdfFunctionFromPdfObject((*array)[3])
  2419  	if err != nil {
  2420  		return nil, err
  2421  	}
  2422  
  2423  	cs.TintTransform = tintTransform
  2424  
  2425  	return cs, nil
  2426  }
  2427  
  2428  func (this *PdfColorspaceSpecialSeparation) ToPdfObject() PdfObject {
  2429  	csArray := MakeArray(MakeName("Separation"))
  2430  
  2431  	csArray.Append(this.ColorantName)
  2432  	csArray.Append(this.AlternateSpace.ToPdfObject())
  2433  	csArray.Append(this.TintTransform.ToPdfObject())
  2434  
  2435  	// If in a container, replace the contents and return back.
  2436  	// Helps not getting too many duplicates of the same objects.
  2437  	if this.container != nil {
  2438  		this.container.PdfObject = csArray
  2439  		return this.container
  2440  	}
  2441  
  2442  	return csArray
  2443  }
  2444  
  2445  func (this *PdfColorspaceSpecialSeparation) ColorFromFloats(vals []float64) (PdfColor, error) {
  2446  	if len(vals) != 1 {
  2447  		return nil, errors.New("Range check")
  2448  	}
  2449  
  2450  	tint := vals[0]
  2451  	input := []float64{tint}
  2452  	output, err := this.TintTransform.Evaluate(input)
  2453  	if err != nil {
  2454  		common.Log.Debug("Error, failed to evaluate: %v", err)
  2455  		common.Log.Trace("Tint transform: %+v", this.TintTransform)
  2456  		return nil, err
  2457  	}
  2458  
  2459  	common.Log.Trace("Processing ColorFromFloats(%+v) on AlternateSpace: %#v", output, this.AlternateSpace)
  2460  	color, err := this.AlternateSpace.ColorFromFloats(output)
  2461  	if err != nil {
  2462  		common.Log.Debug("Error, failed to evaluate in alternate space: %v", err)
  2463  		return nil, err
  2464  	}
  2465  
  2466  	return color, nil
  2467  }
  2468  
  2469  func (this *PdfColorspaceSpecialSeparation) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) {
  2470  	if len(objects) != 1 {
  2471  		return nil, errors.New("Range check")
  2472  	}
  2473  
  2474  	floats, err := getNumbersAsFloat(objects)
  2475  	if err != nil {
  2476  		return nil, err
  2477  	}
  2478  
  2479  	return this.ColorFromFloats(floats)
  2480  }
  2481  
  2482  func (this *PdfColorspaceSpecialSeparation) ColorToRGB(color PdfColor) (PdfColor, error) {
  2483  	if this.AlternateSpace == nil {
  2484  		return nil, errors.New("Alternate colorspace undefined")
  2485  	}
  2486  
  2487  	return this.AlternateSpace.ColorToRGB(color)
  2488  }
  2489  
  2490  // ImageToRGB converts an image with samples in Separation CS to an image with samples specified in
  2491  // DeviceRGB CS.
  2492  func (this *PdfColorspaceSpecialSeparation) ImageToRGB(img Image) (Image, error) {
  2493  	altImage := img
  2494  
  2495  	samples := img.GetSamples()
  2496  	maxVal := math.Pow(2, float64(img.BitsPerComponent)) - 1
  2497  
  2498  	common.Log.Trace("Separation color space -> ToRGB conversion")
  2499  	common.Log.Trace("samples in: %d", len(samples))
  2500  	common.Log.Trace("TintTransform: %+v", this.TintTransform)
  2501  
  2502  	altDecode := this.AlternateSpace.DecodeArray()
  2503  
  2504  	altSamples := []uint32{}
  2505  	// Convert tints to color data in the alternate colorspace.
  2506  	for i := 0; i < len(samples); i++ {
  2507  		// A single tint component is in the range 0.0 - 1.0
  2508  		tint := float64(samples[i]) / maxVal
  2509  
  2510  		// Convert the tint value to the alternate space value.
  2511  		outputs, err := this.TintTransform.Evaluate([]float64{tint})
  2512  		//common.Log.Trace("%v Converting tint value: %f -> [% f]", this.AlternateSpace, tint, outputs)
  2513  
  2514  		if err != nil {
  2515  			return img, err
  2516  		}
  2517  
  2518  		for i, val := range outputs {
  2519  			// Convert component value to 0-1 range.
  2520  			altVal := interpolate(val, altDecode[i*2], altDecode[i*2+1], 0, 1)
  2521  
  2522  			// Rescale to [0, maxVal]
  2523  			altComponent := uint32(altVal * maxVal)
  2524  
  2525  			altSamples = append(altSamples, altComponent)
  2526  		}
  2527  	}
  2528  	common.Log.Trace("Samples out: %d", len(altSamples))
  2529  	altImage.SetSamples(altSamples)
  2530  	altImage.ColorComponents = this.AlternateSpace.GetNumComponents()
  2531  
  2532  	// Set the image's decode parameters for interpretation in the alternative CS.
  2533  	altImage.decode = altDecode
  2534  
  2535  	// Convert to RGB via the alternate colorspace.
  2536  	return this.AlternateSpace.ImageToRGB(altImage)
  2537  }
  2538  
  2539  //////////////////////
  2540  // DeviceN color spaces are similar to Separation color spaces, except they can contain an arbitrary
  2541  // number of color components.
  2542  //
  2543  // Format: [/DeviceN names alternateSpace tintTransform]
  2544  //     or: [/DeviceN names alternateSpace tintTransform attributes]
  2545  type PdfColorspaceDeviceN struct {
  2546  	ColorantNames  *PdfObjectArray
  2547  	AlternateSpace PdfColorspace
  2548  	TintTransform  PdfFunction
  2549  	Attributes     *PdfColorspaceDeviceNAttributes
  2550  
  2551  	// Optional
  2552  	container *PdfIndirectObject
  2553  }
  2554  
  2555  func NewPdfColorspaceDeviceN() *PdfColorspaceDeviceN {
  2556  	cs := &PdfColorspaceDeviceN{}
  2557  	return cs
  2558  }
  2559  
  2560  func (this *PdfColorspaceDeviceN) String() string {
  2561  	return "DeviceN"
  2562  }
  2563  
  2564  // GetNumComponents returns the number of input color components, i.e. that are input to the tint transform.
  2565  func (this *PdfColorspaceDeviceN) GetNumComponents() int {
  2566  	return len(*this.ColorantNames)
  2567  }
  2568  
  2569  // DecodeArray returns the component range values for the DeviceN colorspace.
  2570  // [0 1.0 0 1.0 ...] for each color component.
  2571  func (this *PdfColorspaceDeviceN) DecodeArray() []float64 {
  2572  	decode := []float64{}
  2573  	for i := 0; i < this.GetNumComponents(); i++ {
  2574  		decode = append(decode, 0.0, 1.0)
  2575  	}
  2576  	return decode
  2577  }
  2578  
  2579  func newPdfColorspaceDeviceNFromPdfObject(obj PdfObject) (*PdfColorspaceDeviceN, error) {
  2580  	cs := NewPdfColorspaceDeviceN()
  2581  
  2582  	// If within an indirect object, then make a note of it.  If we write out the PdfObject later
  2583  	// we can reference the same container.  Otherwise is not within a container, but rather
  2584  	// a new array.
  2585  	if indObj, isIndirect := obj.(*PdfIndirectObject); isIndirect {
  2586  		cs.container = indObj
  2587  	}
  2588  
  2589  	// Check the CS array.
  2590  	obj = TraceToDirectObject(obj)
  2591  	csArray, ok := obj.(*PdfObjectArray)
  2592  	if !ok {
  2593  		return nil, fmt.Errorf("DeviceN CS: Invalid object")
  2594  	}
  2595  
  2596  	if len(*csArray) != 4 && len(*csArray) != 5 {
  2597  		return nil, fmt.Errorf("DeviceN CS: Incorrect array length")
  2598  	}
  2599  
  2600  	// Check name.
  2601  	obj = (*csArray)[0]
  2602  	name, ok := obj.(*PdfObjectName)
  2603  	if !ok {
  2604  		return nil, fmt.Errorf("DeviceN CS: invalid family name")
  2605  	}
  2606  	if *name != "DeviceN" {
  2607  		return nil, fmt.Errorf("DeviceN CS: wrong family name")
  2608  	}
  2609  
  2610  	// Get colorant names.  Specifies the number of components too.
  2611  	obj = (*csArray)[1]
  2612  	obj = TraceToDirectObject(obj)
  2613  	nameArray, ok := obj.(*PdfObjectArray)
  2614  	if !ok {
  2615  		return nil, fmt.Errorf("DeviceN CS: Invalid names array")
  2616  	}
  2617  	cs.ColorantNames = nameArray
  2618  
  2619  	// Get base colormap.
  2620  	obj = (*csArray)[2]
  2621  	alternativeCs, err := NewPdfColorspaceFromPdfObject(obj)
  2622  	if err != nil {
  2623  		return nil, err
  2624  	}
  2625  	cs.AlternateSpace = alternativeCs
  2626  
  2627  	// Tint transform is specified by a PDF function.
  2628  	tintTransform, err := newPdfFunctionFromPdfObject((*csArray)[3])
  2629  	if err != nil {
  2630  		return nil, err
  2631  	}
  2632  	cs.TintTransform = tintTransform
  2633  
  2634  	// Attributes.
  2635  	if len(*csArray) == 5 {
  2636  		attr, err := newPdfColorspaceDeviceNAttributesFromPdfObject((*csArray)[4])
  2637  		if err != nil {
  2638  			return nil, err
  2639  		}
  2640  		cs.Attributes = attr
  2641  	}
  2642  
  2643  	return cs, nil
  2644  }
  2645  
  2646  // Format: [/DeviceN names alternateSpace tintTransform]
  2647  //     or: [/DeviceN names alternateSpace tintTransform attributes]
  2648  
  2649  func (this *PdfColorspaceDeviceN) ToPdfObject() PdfObject {
  2650  	csArray := MakeArray(MakeName("DeviceN"))
  2651  	csArray.Append(this.ColorantNames)
  2652  	csArray.Append(this.AlternateSpace.ToPdfObject())
  2653  	csArray.Append(this.TintTransform.ToPdfObject())
  2654  	if this.Attributes != nil {
  2655  		csArray.Append(this.Attributes.ToPdfObject())
  2656  	}
  2657  
  2658  	if this.container != nil {
  2659  		this.container.PdfObject = csArray
  2660  		return this.container
  2661  	}
  2662  
  2663  	return csArray
  2664  }
  2665  
  2666  func (this *PdfColorspaceDeviceN) ColorFromFloats(vals []float64) (PdfColor, error) {
  2667  	if len(vals) != this.GetNumComponents() {
  2668  		return nil, errors.New("Range check")
  2669  	}
  2670  
  2671  	output, err := this.TintTransform.Evaluate(vals)
  2672  	if err != nil {
  2673  		return nil, err
  2674  	}
  2675  
  2676  	color, err := this.AlternateSpace.ColorFromFloats(output)
  2677  	if err != nil {
  2678  		return nil, err
  2679  	}
  2680  	return color, nil
  2681  }
  2682  
  2683  func (this *PdfColorspaceDeviceN) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) {
  2684  	if len(objects) != this.GetNumComponents() {
  2685  		return nil, errors.New("Range check")
  2686  	}
  2687  
  2688  	floats, err := getNumbersAsFloat(objects)
  2689  	if err != nil {
  2690  		return nil, err
  2691  	}
  2692  
  2693  	return this.ColorFromFloats(floats)
  2694  }
  2695  
  2696  func (this *PdfColorspaceDeviceN) ColorToRGB(color PdfColor) (PdfColor, error) {
  2697  	if this.AlternateSpace == nil {
  2698  		return nil, errors.New("DeviceN alternate space undefined")
  2699  	}
  2700  	return this.AlternateSpace.ColorToRGB(color)
  2701  }
  2702  
  2703  func (this *PdfColorspaceDeviceN) ImageToRGB(img Image) (Image, error) {
  2704  	altImage := img
  2705  
  2706  	samples := img.GetSamples()
  2707  	maxVal := math.Pow(2, float64(img.BitsPerComponent)) - 1
  2708  
  2709  	// Convert tints to color data in the alternate colorspace.
  2710  	altSamples := []uint32{}
  2711  	for i := 0; i < len(samples); i += this.GetNumComponents() {
  2712  		// The input to the tint transformation is the tint
  2713  		// for each color component.
  2714  		//
  2715  		// A single tint component is in the range 0.0 - 1.0
  2716  		inputs := []float64{}
  2717  		for j := 0; j < this.GetNumComponents(); j++ {
  2718  			tint := float64(samples[i+j]) / maxVal
  2719  			inputs = append(inputs, tint)
  2720  		}
  2721  
  2722  		// Transform the tints to the alternate colorspace.
  2723  		// (scaled units).
  2724  		outputs, err := this.TintTransform.Evaluate(inputs)
  2725  		if err != nil {
  2726  			return img, err
  2727  		}
  2728  
  2729  		for _, val := range outputs {
  2730  			// Clip.
  2731  			val = math.Min(math.Max(0, val), 1.0)
  2732  			// Rescale to [0, maxVal]
  2733  			altComponent := uint32(val * maxVal)
  2734  			altSamples = append(altSamples, altComponent)
  2735  		}
  2736  	}
  2737  	altImage.SetSamples(altSamples)
  2738  
  2739  	// Convert to RGB via the alternate colorspace.
  2740  	return this.AlternateSpace.ImageToRGB(altImage)
  2741  }
  2742  
  2743  // Additional information about the components of colour space that conforming readers may use.
  2744  // Conforming readers need not use the alternateSpace and tintTransform parameters, and may
  2745  // instead use custom blending algorithms, along with other information provided in the attributes
  2746  // dictionary if present.
  2747  type PdfColorspaceDeviceNAttributes struct {
  2748  	Subtype     *PdfObjectName // DeviceN or NChannel (DeviceN default)
  2749  	Colorants   PdfObject
  2750  	Process     PdfObject
  2751  	MixingHints PdfObject
  2752  
  2753  	// Optional
  2754  	container *PdfIndirectObject
  2755  }
  2756  
  2757  func newPdfColorspaceDeviceNAttributesFromPdfObject(obj PdfObject) (*PdfColorspaceDeviceNAttributes, error) {
  2758  	attr := &PdfColorspaceDeviceNAttributes{}
  2759  
  2760  	var dict *PdfObjectDictionary
  2761  	if indObj, isInd := obj.(*PdfIndirectObject); isInd {
  2762  		attr.container = indObj
  2763  		var ok bool
  2764  		dict, ok = indObj.PdfObject.(*PdfObjectDictionary)
  2765  		if !ok {
  2766  			common.Log.Error("DeviceN attribute type error")
  2767  			return nil, errors.New("Type error")
  2768  		}
  2769  	} else if d, isDict := obj.(*PdfObjectDictionary); isDict {
  2770  		dict = d
  2771  	} else {
  2772  		common.Log.Error("DeviceN attribute type error")
  2773  		return nil, errors.New("Type error")
  2774  	}
  2775  
  2776  	if obj := dict.Get("Subtype"); obj != nil {
  2777  		name, ok := TraceToDirectObject(obj).(*PdfObjectName)
  2778  		if !ok {
  2779  			common.Log.Error("DeviceN attribute Subtype type error")
  2780  			return nil, errors.New("Type error")
  2781  		}
  2782  
  2783  		attr.Subtype = name
  2784  	}
  2785  
  2786  	if obj := dict.Get("Colorants"); obj != nil {
  2787  		attr.Colorants = obj
  2788  	}
  2789  
  2790  	if obj := dict.Get("Process"); obj != nil {
  2791  		attr.Process = obj
  2792  	}
  2793  
  2794  	if obj := dict.Get("MixingHints"); obj != nil {
  2795  		attr.MixingHints = obj
  2796  	}
  2797  
  2798  	return attr, nil
  2799  }
  2800  
  2801  func (this *PdfColorspaceDeviceNAttributes) ToPdfObject() PdfObject {
  2802  	dict := MakeDict()
  2803  
  2804  	if this.Subtype != nil {
  2805  		dict.Set("Subtype", this.Subtype)
  2806  	}
  2807  	dict.SetIfNotNil("Colorants", this.Colorants)
  2808  	dict.SetIfNotNil("Process", this.Process)
  2809  	dict.SetIfNotNil("MixingHints", this.MixingHints)
  2810  
  2811  	if this.container != nil {
  2812  		this.container.PdfObject = dict
  2813  		return this.container
  2814  	}
  2815  
  2816  	return dict
  2817  }