Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
Functions
SkICC.cpp File Reference
#include "include/encode/SkICC.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkData.h"
#include "include/core/SkStream.h"
#include "include/core/SkString.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkFixed.h"
#include "include/private/base/SkFloatingPoint.h"
#include "modules/skcms/skcms.h"
#include "src/base/SkAutoMalloc.h"
#include "src/base/SkEndian.h"
#include "src/core/SkMD5.h"
#include "src/encode/SkICCPriv.h"
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <utility>
#include <vector>

Go to the source code of this file.

Functions

sk_sp< SkDataSkWriteICCProfile (const skcms_ICCProfile *profile, const char *desc)
 
sk_sp< SkDataSkWriteICCProfile (const skcms_TransferFunction &fn, const skcms_Matrix3x3 &toXYZD50)
 

Function Documentation

◆ SkWriteICCProfile() [1/2]

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

Definition at line 542 of file SkICC.cpp.

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

◆ SkWriteICCProfile() [2/2]

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

Definition at line 679 of file SkICC.cpp.

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