Flutter Engine
The Flutter Engine
Functions
SkICC.h File Reference
#include "include/core/SkRefCnt.h"
#include "include/private/base/SkAPI.h"
#include <cstdint>

Go to the source code of this file.

Functions

SK_API sk_sp< SkDataSkWriteICCProfile (const skcms_TransferFunction &, const skcms_Matrix3x3 &toXYZD50)
 
SK_API sk_sp< SkDataSkWriteICCProfile (const skcms_ICCProfile *, const char *description)
 
SK_API void SkICCFloatXYZD50ToGrid16Lab (const float *float_xyz, uint8_t *grid16_lab)
 
SK_API void SkICCFloatToTable16 (const float f, uint8_t *table_16)
 

Function Documentation

◆ SkICCFloatToTable16()

SK_API void SkICCFloatToTable16 ( const float  f,
uint8_t *  table_16 
)

◆ SkICCFloatXYZD50ToGrid16Lab()

SK_API void SkICCFloatXYZD50ToGrid16Lab ( const float *  float_xyz,
uint8_t *  grid16_lab 
)

◆ SkWriteICCProfile() [1/2]

SK_API sk_sp< SkData > SkWriteICCProfile ( const skcms_ICCProfile profile,
const char *  description 
)

Definition at line 545 of file SkICC.cpp.

545 {
546 ICCHeader header;
547
548 std::vector<std::pair<uint32_t, sk_sp<SkData>>> tags;
549
550 // Compute primaries.
551 if (profile->has_toXYZD50) {
552 const auto& m = profile->toXYZD50;
553 tags.emplace_back(kTAG_rXYZ, write_xyz_tag(m.vals[0][0], m.vals[1][0], m.vals[2][0]));
554 tags.emplace_back(kTAG_gXYZ, write_xyz_tag(m.vals[0][1], m.vals[1][1], m.vals[2][1]));
555 tags.emplace_back(kTAG_bXYZ, write_xyz_tag(m.vals[0][2], m.vals[1][2], m.vals[2][2]));
556 }
557
558 // Compute white point tag (must be D50)
559 tags.emplace_back(kTAG_wtpt, write_xyz_tag(kD50_x, kD50_y, kD50_z));
560
561 // Compute transfer curves.
562 if (profile->has_trc) {
563 tags.emplace_back(kTAG_rTRC, write_trc_tag(profile->trc[0]));
564
565 // Use empty data to indicate that the entry should use the previous tag's
566 // data.
567 if (!memcmp(&profile->trc[1], &profile->trc[0], sizeof(profile->trc[0]))) {
568 tags.emplace_back(kTAG_gTRC, SkData::MakeEmpty());
569 } else {
570 tags.emplace_back(kTAG_gTRC, write_trc_tag(profile->trc[1]));
571 }
572
573 if (!memcmp(&profile->trc[2], &profile->trc[1], sizeof(profile->trc[1]))) {
574 tags.emplace_back(kTAG_bTRC, SkData::MakeEmpty());
575 } else {
576 tags.emplace_back(kTAG_bTRC, write_trc_tag(profile->trc[2]));
577 }
578 }
579
580 // Compute CICP.
581 if (profile->has_CICP) {
582 // The CICP tag is present in ICC 4.4, so update the header's version.
583 header.version = SkEndian_SwapBE32(0x04400000);
584 tags.emplace_back(kTAG_cicp, write_cicp_tag(profile->CICP));
585 }
586
587 // Compute A2B0.
588 if (profile->has_A2B) {
589 const auto& a2b = profile->A2B;
590 SkASSERT(a2b.output_channels == kNumChannels);
591 auto a2b_data = write_mAB_or_mBA_tag(kTAG_mABType,
592 a2b.output_curves,
593 a2b.input_channels ? a2b.input_curves : nullptr,
594 a2b.input_channels ? a2b.grid_points : nullptr,
595 a2b.input_channels ? a2b.grid_16 : nullptr,
596 a2b.matrix_channels ? a2b.matrix_curves : nullptr,
597 a2b.matrix_channels ? &a2b.matrix : nullptr);
598 tags.emplace_back(kTAG_A2B0, std::move(a2b_data));
599 }
600
601 // Compute B2A0.
602 if (profile->has_B2A) {
603 const auto& b2a = profile->B2A;
604 SkASSERT(b2a.input_channels == kNumChannels);
605 auto b2a_data = write_mAB_or_mBA_tag(kTAG_mBAType,
606 b2a.input_curves,
607 b2a.output_channels ? b2a.input_curves : nullptr,
608 b2a.output_channels ? b2a.grid_points : nullptr,
609 b2a.output_channels ? b2a.grid_16 : nullptr,
610 b2a.matrix_channels ? b2a.matrix_curves : nullptr,
611 b2a.matrix_channels ? &b2a.matrix : nullptr);
612 tags.emplace_back(kTAG_B2A0, std::move(b2a_data));
613 }
614
615 // Compute copyright tag
616 tags.emplace_back(kTAG_cprt, write_text_tag("Google Inc. 2016"));
617
618 // Ensure that the desc isn't empty https://crbug.com/329032158
619 std::string generatedDesc;
620 if (!desc || *desc == '\0') {
621 SkMD5 md5;
622 for (const auto& tag : tags) {
623 md5.write(&tag.first, sizeof(tag.first));
624 md5.write(tag.second->bytes(), tag.second->size());
625 }
626 SkMD5::Digest digest = md5.finish();
627 generatedDesc = std::string("Google/Skia/") + digest.toHexString().c_str();
628 desc = generatedDesc.c_str();
629 }
630 // Compute profile description tag
631 tags.emplace(tags.begin(), kTAG_desc, write_text_tag(desc));
632
633 // Compute the size of the profile.
634 size_t tag_data_size = 0;
635 for (const auto& tag : tags) {
636 tag_data_size += tag.second->size();
637 }
638 size_t tag_table_size = kICCTagTableEntrySize * tags.size();
639 size_t profile_size = kICCHeaderSize + tag_table_size + tag_data_size;
640
641 // Write the header.
642 header.data_color_space = SkEndian_SwapBE32(profile->data_color_space);
643 header.pcs = SkEndian_SwapBE32(profile->pcs);
644 header.size = SkEndian_SwapBE32(profile_size);
645 header.tag_count = SkEndian_SwapBE32(tags.size());
646
647 SkAutoMalloc profile_data(profile_size);
648 uint8_t* ptr = (uint8_t*)profile_data.get();
649 memcpy(ptr, &header, sizeof(header));
650 ptr += sizeof(header);
651
652 // Write the tag table. Track the offset and size of the previous tag to
653 // compute each tag's offset. An empty SkData indicates that the previous
654 // tag is to be reused.
655 size_t last_tag_offset = sizeof(header) + tag_table_size;
656 size_t last_tag_size = 0;
657 for (const auto& tag : tags) {
658 if (!tag.second->isEmpty()) {
659 last_tag_offset = last_tag_offset + last_tag_size;
660 last_tag_size = tag.second->size();
661 }
662 uint32_t tag_table_entry[3] = {
663 SkEndian_SwapBE32(tag.first),
664 SkEndian_SwapBE32(last_tag_offset),
665 SkEndian_SwapBE32(last_tag_size),
666 };
667 memcpy(ptr, tag_table_entry, sizeof(tag_table_entry));
668 ptr += sizeof(tag_table_entry);
669 }
670
671 // Write the tags.
672 for (const auto& tag : tags) {
673 if (tag.second->isEmpty()) continue;
674 memcpy(ptr, tag.second->data(), tag.second->size());
675 ptr += tag.second->size();
676 }
677
678 SkASSERT(profile_size == static_cast<size_t>(ptr - (uint8_t*)profile_data.get()));
679 return SkData::MakeFromMalloc(profile_data.release(), profile_size);
680}
static SkMD5::Digest md5(const SkBitmap &bm)
Definition: CodecTest.cpp:77
#define SkASSERT(cond)
Definition: SkAssert.h:116
#define SkEndian_SwapBE32(n)
Definition: SkEndian.h:136
static constexpr uint32_t kTAG_mBAType
Definition: SkICCPriv.h:53
static constexpr uint32_t kTAG_desc
Definition: SkICCPriv.h:44
static constexpr uint32_t kTAG_gTRC
Definition: SkICCPriv.h:39
static constexpr uint32_t kTAG_cprt
Definition: SkICCPriv.h:47
static constexpr uint32_t kTAG_bTRC
Definition: SkICCPriv.h:40
static constexpr size_t kICCHeaderSize
Definition: SkICCPriv.h:19
static constexpr uint32_t kTAG_gXYZ
Definition: SkICCPriv.h:36
static constexpr uint32_t kTAG_wtpt
Definition: SkICCPriv.h:46
static constexpr uint32_t kTAG_A2B0
Definition: SkICCPriv.h:42
static constexpr uint32_t kTAG_B2A0
Definition: SkICCPriv.h:43
static constexpr size_t kICCTagTableEntrySize
Definition: SkICCPriv.h:22
static constexpr uint32_t kTAG_mABType
Definition: SkICCPriv.h:52
static constexpr uint32_t kTAG_bXYZ
Definition: SkICCPriv.h:37
static constexpr uint32_t kTAG_rXYZ
Definition: SkICCPriv.h:35
static constexpr uint32_t kTAG_rTRC
Definition: SkICCPriv.h:38
static constexpr uint32_t kTAG_cicp
Definition: SkICCPriv.h:45
static sk_sp< SkData > MakeFromMalloc(const void *data, size_t length)
Definition: SkData.cpp:107
static sk_sp< SkData > MakeEmpty()
Definition: SkData.cpp:94
Definition: SkMD5.h:19
const char * c_str() const
Definition: SkString.h:133
static const char header[]
Definition: skpbench.cpp:88
SkString toHexString() const
Definition: SkMD5.cpp:112

◆ SkWriteICCProfile() [2/2]

SK_API sk_sp< SkData > SkWriteICCProfile ( const skcms_TransferFunction fn,
const skcms_Matrix3x3 toXYZD50 
)

Definition at line 682 of file SkICC.cpp.

682 {
684 memset(&profile, 0, sizeof(profile));
685 std::vector<uint16_t> trc_table;
686 std::vector<uint16_t> a2b_grid;
687
688 profile.data_color_space = skcms_Signature_RGB;
690
691 // Populate toXYZD50.
692 {
693 profile.has_toXYZD50 = true;
694 profile.toXYZD50 = toXYZD50;
695 }
696
697 // Populate the analytic TRC for sRGB-like curves.
699 profile.has_trc = true;
700 profile.trc[0].table_entries = 0;
701 profile.trc[0].parametric = fn;
702 memcpy(&profile.trc[1], &profile.trc[0], sizeof(profile.trc[0]));
703 memcpy(&profile.trc[2], &profile.trc[0], sizeof(profile.trc[0]));
704 }
705
706 // Populate A2B (PQ and HLG only).
708 // Populate a 1D curve to perform per-channel conversion to linear and tone mapping.
709 constexpr uint32_t kTrcTableSize = 65;
710 trc_table.resize(kTrcTableSize);
711 for (uint32_t i = 0; i < kTrcTableSize; ++i) {
712 float x = i / (kTrcTableSize - 1.f);
713 x = hdr_trfn_eval(fn, x);
714 x *= tone_map_gain(x);
715 trc_table[i] = SkEndian_SwapBE16(float_to_uInt16Number(x, kOne16CurveType));
716 }
717
718 // Populate the grid with a 3D LUT to do cross-channel tone mapping.
719 constexpr uint32_t kGridSize = 11;
720 a2b_grid.resize(kGridSize * kGridSize * kGridSize * kNumChannels);
721 size_t a2b_grid_index = 0;
722 for (uint32_t r_index = 0; r_index < kGridSize; ++r_index) {
723 for (uint32_t g_index = 0; g_index < kGridSize; ++g_index) {
724 for (uint32_t b_index = 0; b_index < kGridSize; ++b_index) {
725 float rgb[3] = {
726 r_index / (kGridSize - 1.f),
727 g_index / (kGridSize - 1.f),
728 b_index / (kGridSize - 1.f),
729 };
730
731 // Un-apply the per-channel tone mapping.
732 for (auto& c : rgb) {
733 c = tone_map_inverse(c);
734 }
735
736 // For HLG, mix the channels according to the OOTF.
738 // Scale to [0, 1].
739 for (auto& c : rgb) {
740 c /= kToneMapInputMax;
741 }
742
743 // Un-apply the per-channel OOTF.
744 for (auto& c : rgb) {
745 c = std::pow(c, 1 / 1.2);
746 }
747
748 // Re-apply the cross-channel OOTF.
749 float Y = 0.2627f * rgb[0] + 0.6780f * rgb[1] + 0.0593f * rgb[2];
750 for (auto& c : rgb) {
751 c *= std::pow(Y, 0.2);
752 }
753
754 // Scale back up to 1.0 being 1,000/203.
755 for (auto& c : rgb) {
756 c *= kToneMapInputMax;
757 }
758 }
759
760 // Apply tone mapping to take 1,000/203 to 1.0.
761 {
762 float max_rgb = std::max(std::max(rgb[0], rgb[1]), rgb[2]);
763 for (auto& c : rgb) {
764 c *= tone_map_gain(0.5 * (c + max_rgb));
765 c = std::min(c, 1.f);
766 }
767 }
768
769 // Write the result to the LUT.
770 for (const auto& c : rgb) {
771 a2b_grid[a2b_grid_index++] =
772 SkEndian_SwapBE16(float_to_uInt16Number(c, kOne16XYZ));
773 }
774 }
775 }
776 }
777
778 // Populate A2B as this tone mapping.
779 profile.has_A2B = true;
780 profile.A2B.input_channels = kNumChannels;
781 profile.A2B.output_channels = kNumChannels;
782 profile.A2B.matrix_channels = kNumChannels;
783 for (size_t i = 0; i < kNumChannels; ++i) {
784 profile.A2B.grid_points[i] = kGridSize;
785 // Set the input curve to convert to linear pre-OOTF space.
786 profile.A2B.input_curves[i].table_entries = kTrcTableSize;
787 profile.A2B.input_curves[i].table_16 = reinterpret_cast<uint8_t*>(trc_table.data());
788 // The output and matrix curves are the identity.
789 profile.A2B.output_curves[i].parametric = SkNamedTransferFn::kLinear;
790 profile.A2B.matrix_curves[i].parametric = SkNamedTransferFn::kLinear;
791 // Set the matrix to convert from the primaries to XYZD50.
792 for (size_t j = 0; j < 3; ++j) {
793 profile.A2B.matrix.vals[i][j] = toXYZD50.vals[i][j];
794 }
795 profile.A2B.matrix.vals[i][3] = 0.f;
796 }
797 profile.A2B.grid_16 = reinterpret_cast<const uint8_t*>(a2b_grid.data());
798
799 // Populate B2A as the identity.
800 profile.has_B2A = true;
801 profile.B2A.input_channels = kNumChannels;
802 for (size_t i = 0; i < 3; ++i) {
803 profile.B2A.input_curves[i].parametric = SkNamedTransferFn::kLinear;
804 }
805 }
806
807 // Populate CICP.
809 profile.has_CICP = true;
810 profile.CICP.color_primaries = get_cicp_primaries(toXYZD50);
811 profile.CICP.transfer_characteristics = get_cicp_trfn(fn);
812 profile.CICP.matrix_coefficients = 0;
813 profile.CICP.video_full_range_flag = 1;
814 SkASSERT(profile.CICP.color_primaries);
815 SkASSERT(profile.CICP.transfer_characteristics);
816 }
817
818 std::string description = get_desc_string(fn, toXYZD50);
819 return SkWriteICCProfile(&profile, description.c_str());
820}
#define SkEndian_SwapBE16(n)
Definition: SkEndian.h:135
sk_sp< SkData > SkWriteICCProfile(const skcms_ICCProfile *profile, const char *desc)
Definition: SkICC.cpp:545
static const SkScalar Y
Definition: StrokeBench.cpp:55
static float max(float r, float g, float b)
Definition: hsl.cpp:49
static float min(float r, float g, float b)
Definition: hsl.cpp:48
double x
static constexpr skcms_TransferFunction kLinear
Definition: SkColorSpace.h:51
bool skcms_TransferFunction_isHLGish(const skcms_TransferFunction *tf)
Definition: skcms.cc:192
bool skcms_TransferFunction_isPQish(const skcms_TransferFunction *tf)
Definition: skcms.cc:189
bool skcms_TransferFunction_isSRGBish(const skcms_TransferFunction *tf)
Definition: skcms.cc:186
@ skcms_Signature_RGB
Definition: skcms_public.h:266
@ skcms_Signature_XYZ
Definition: skcms_public.h:270
float vals[3][3]
Definition: skcms_public.h:27