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 }