1 /* 2 * Copyright 2016 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "SkColorSpaceXform_A2B.h" 9 10 #include "SkColorData.h" 11 #include "SkColorSpace_A2B.h" 12 #include "SkColorSpace_XYZ.h" 13 #include "SkColorSpacePriv.h" 14 #include "SkColorSpaceXformPriv.h" 15 #include "SkMakeUnique.h" 16 #include "SkNx.h" 17 #include "SkSRGB.h" 18 #include "SkTypes.h" 19 #include "../jumper/SkJumper.h" 20 21 bool SkColorSpaceXform_A2B::onApply(ColorFormat dstFormat, void* dst, ColorFormat srcFormat, 22 const void* src, int count, SkAlphaType alphaType) const { 23 SkRasterPipeline_<256> pipeline; 24 25 SkJumper_MemoryCtx src_ctx = { (void*)src, 0 }, 26 dst_ctx = { (void*)dst, 0 }; 27 28 switch (srcFormat) { 29 case kBGRA_8888_ColorFormat: 30 pipeline.append(SkRasterPipeline::load_bgra, &src_ctx); 31 break; 32 case kRGBA_8888_ColorFormat: 33 pipeline.append(SkRasterPipeline::load_8888, &src_ctx); 34 break; 35 case kRGBA_U16_BE_ColorFormat: 36 pipeline.append(SkRasterPipeline::load_u16_be, &src_ctx); 37 break; 38 case kRGB_U16_BE_ColorFormat: 39 pipeline.append(SkRasterPipeline::load_rgb_u16_be, &src_ctx); 40 break; 41 default: 42 SkCSXformPrintf("F16/F32 sources must be linear.\n"); 43 return false; 44 } 45 46 pipeline.extend(fElementsPipeline); 47 48 if (kPremul_SkAlphaType == alphaType) { 49 pipeline.append(SkRasterPipeline::premul); 50 } 51 52 switch (dstFormat) { 53 case kBGRA_8888_ColorFormat: 54 pipeline.append(SkRasterPipeline::store_bgra, &dst_ctx); 55 break; 56 case kRGBA_8888_ColorFormat: 57 pipeline.append(SkRasterPipeline::store_8888, &dst_ctx); 58 break; 59 case kRGBA_F16_ColorFormat: 60 if (!fLinearDstGamma) { 61 return false; 62 } 63 pipeline.append(SkRasterPipeline::store_f16, &dst_ctx); 64 break; 65 case kRGBA_F32_ColorFormat: 66 if (!fLinearDstGamma) { 67 return false; 68 } 69 pipeline.append(SkRasterPipeline::store_f32, &dst_ctx); 70 break; 71 case kBGR_565_ColorFormat: 72 if (kOpaque_SkAlphaType != alphaType) { 73 return false; 74 } 75 pipeline.append(SkRasterPipeline::store_565, &dst_ctx); 76 break; 77 default: 78 return false; 79 } 80 pipeline.run(0,0, count,1); 81 82 return true; 83 } 84 85 static inline bool gamma_to_parametric(SkColorSpaceTransferFn* coeffs, const SkGammas& gammas, 86 int channel) { 87 switch (gammas.type(channel)) { 88 case SkGammas::Type::kNamed_Type: 89 return named_to_parametric(coeffs, gammas.data(channel).fNamed); 90 case SkGammas::Type::kValue_Type: 91 value_to_parametric(coeffs, gammas.data(channel).fValue); 92 return true; 93 case SkGammas::Type::kParam_Type: 94 *coeffs = gammas.params(channel); 95 return true; 96 default: 97 return false; 98 } 99 } 100 101 SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace, 102 SkColorSpace_XYZ* dstSpace) 103 : fElementsPipeline(&fAlloc) 104 , fLinearDstGamma(kLinear_SkGammaNamed == dstSpace->gammaNamed()) { 105 #if (SkCSXformPrintfDefined) 106 static const char* debugGammaNamed[4] = { 107 "Linear", "SRGB", "2.2", "NonStandard" 108 }; 109 static const char* debugGammas[5] = { 110 "None", "Named", "Value", "Table", "Param" 111 }; 112 #endif 113 int currentChannels; 114 switch (srcSpace->iccType()) { 115 case SkColorSpace::kRGB_Type: 116 currentChannels = 3; 117 break; 118 case SkColorSpace::kCMYK_Type: { 119 currentChannels = 4; 120 // CMYK images from JPEGs (the only format that supports it) are actually 121 // inverted CMYK, so we need to invert every channel. 122 fElementsPipeline.append(SkRasterPipeline::invert); 123 break; 124 } 125 default: 126 currentChannels = 0; 127 SkASSERT(false); 128 } 129 // add in all input color space -> PCS xforms 130 for (int i = 0; i < srcSpace->count(); ++i) { 131 const SkColorSpace_A2B::Element& e = srcSpace->element(i); 132 SkASSERT(e.inputChannels() == currentChannels); 133 currentChannels = e.outputChannels(); 134 switch (e.type()) { 135 case SkColorSpace_A2B::Element::Type::kGammaNamed: { 136 if (kLinear_SkGammaNamed == e.gammaNamed()) { 137 break; 138 } 139 140 // Take the fast path for ordinary sRGB. 141 if (3 == currentChannels && kSRGB_SkGammaNamed == e.gammaNamed()) { 142 SkCSXformPrintf("fast path from sRGB\n"); 143 fElementsPipeline.append(SkRasterPipeline::from_srgb); 144 break; 145 } 146 147 SkCSXformPrintf("Gamma stage added: %s\n", debugGammaNamed[(int)e.gammaNamed()]); 148 auto fn = fAlloc.make<SkColorSpaceTransferFn>(); 149 SkAssertResult(named_to_parametric(fn, e.gammaNamed())); 150 151 if (is_just_gamma(*fn)) { 152 fElementsPipeline.append(SkRasterPipeline::gamma, &fn->fG); 153 } else { 154 fElementsPipeline.append(SkRasterPipeline::parametric_r, fn); 155 fElementsPipeline.append(SkRasterPipeline::parametric_g, fn); 156 fElementsPipeline.append(SkRasterPipeline::parametric_b, fn); 157 } 158 break; 159 } 160 case SkColorSpace_A2B::Element::Type::kGammas: { 161 const SkGammas& gammas = e.gammas(); 162 SkCSXformPrintf("Gamma stage added:"); 163 for (int channel = 0; channel < gammas.channels(); ++channel) { 164 SkCSXformPrintf(" %s", debugGammas[(int)gammas.type(channel)]); 165 } 166 SkCSXformPrintf("\n"); 167 bool gammaNeedsRef = false; 168 for (int channel = 0; channel < gammas.channels(); ++channel) { 169 if (SkGammas::Type::kTable_Type == gammas.type(channel)) { 170 SkTableTransferFn table = { 171 gammas.table(channel), 172 gammas.data(channel).fTable.fSize, 173 }; 174 175 gammaNeedsRef |= !this->buildTableFn(&table); 176 this->addTableFn(table, channel); 177 } else { 178 SkColorSpaceTransferFn fn; 179 SkAssertResult(gamma_to_parametric(&fn, gammas, channel)); 180 this->addTransferFn(fn, channel); 181 } 182 } 183 if (gammaNeedsRef) { 184 this->copy(sk_ref_sp(&gammas)); 185 } 186 break; 187 } 188 case SkColorSpace_A2B::Element::Type::kCLUT: { 189 SkCSXformPrintf("CLUT (%d -> %d) stage added\n", e.colorLUT().inputChannels(), 190 e.colorLUT().outputChannels()); 191 192 struct Ctx : SkJumper_ColorLookupTableCtx { 193 sk_sp<const SkColorLookUpTable> clut; 194 }; 195 auto ctx = fAlloc.make<Ctx>(); 196 ctx->clut = sk_ref_sp(&e.colorLUT()); 197 ctx->table = ctx->clut->table(); 198 for (int i = 0; i < ctx->clut->inputChannels(); i++) { 199 ctx->limits[i] = ctx->clut->gridPoints(i); 200 } 201 202 switch (e.colorLUT().inputChannels()) { 203 case 3: fElementsPipeline.append(SkRasterPipeline::clut_3D, ctx); break; 204 case 4: fElementsPipeline.append(SkRasterPipeline::clut_4D, ctx); break; 205 default: SkDEBUGFAIL("need to handle 1 or 2 channel color lookup tables."); 206 } 207 fElementsPipeline.append(SkRasterPipeline::clamp_0); 208 fElementsPipeline.append(SkRasterPipeline::clamp_1); 209 break; 210 } 211 case SkColorSpace_A2B::Element::Type::kMatrix: 212 if (!e.matrix().isIdentity()) { 213 SkCSXformPrintf("Matrix stage added\n"); 214 addMatrix(e.matrix()); 215 } 216 break; 217 } 218 } 219 220 // Lab PCS -> XYZ PCS 221 if (SkColorSpace_A2B::PCS::kLAB == srcSpace->pcs()) { 222 SkCSXformPrintf("Lab -> XYZ element added\n"); 223 fElementsPipeline.append(SkRasterPipeline::lab_to_xyz); 224 } 225 226 // we should now be in XYZ PCS 227 SkASSERT(3 == currentChannels); 228 229 // and XYZ PCS -> output color space xforms 230 if (!dstSpace->fromXYZD50()->isIdentity()) { 231 addMatrix(*dstSpace->fromXYZD50()); 232 } 233 234 switch (dstSpace->gammaNamed()) { 235 case kLinear_SkGammaNamed: 236 // do nothing 237 break; 238 case k2Dot2Curve_SkGammaNamed: { 239 fElementsPipeline.append(SkRasterPipeline::gamma, this->copy(1/2.2f)); 240 break; 241 } 242 case kSRGB_SkGammaNamed: 243 fElementsPipeline.append(SkRasterPipeline::to_srgb); 244 break; 245 case kNonStandard_SkGammaNamed: { 246 for (int channel = 0; channel < 3; ++channel) { 247 const SkGammas& gammas = *dstSpace->gammas(); 248 if (SkGammas::Type::kTable_Type == gammas.type(channel)) { 249 static constexpr int kInvTableSize = 256; 250 auto storage = fAlloc.makeArray<float>(kInvTableSize); 251 invert_table_gamma(storage, nullptr, kInvTableSize, 252 gammas.table(channel), 253 gammas.data(channel).fTable.fSize); 254 SkTableTransferFn table = { storage, kInvTableSize }; 255 this->addTableFn(table, channel); 256 } else { 257 SkColorSpaceTransferFn fn; 258 SkAssertResult(gamma_to_parametric(&fn, gammas, channel)); 259 this->addTransferFn(fn.invert(), channel); 260 } 261 } 262 } 263 break; 264 } 265 } 266 267 void SkColorSpaceXform_A2B::addTransferFn(const SkColorSpaceTransferFn& fn, int channelIndex) { 268 switch (channelIndex) { 269 case 0: 270 fElementsPipeline.append(SkRasterPipeline::parametric_r, this->copy(fn)); 271 break; 272 case 1: 273 fElementsPipeline.append(SkRasterPipeline::parametric_g, this->copy(fn)); 274 break; 275 case 2: 276 fElementsPipeline.append(SkRasterPipeline::parametric_b, this->copy(fn)); 277 break; 278 case 3: 279 fElementsPipeline.append(SkRasterPipeline::parametric_a, this->copy(fn)); 280 break; 281 default: 282 SkASSERT(false); 283 } 284 } 285 286 /** 287 * |fn| is an in-out parameter. If the table is too small to perform reasonable table-lookups 288 * without interpolation, we will build a bigger table. 289 * 290 * This returns false if we use the original table, meaning we do nothing here but need to keep 291 * a reference to the original table. This returns true if we build a new table and the original 292 * table can be discarded. 293 */ 294 bool SkColorSpaceXform_A2B::buildTableFn(SkTableTransferFn* fn) { 295 // Arbitrary, but seems like a reasonable guess. 296 static constexpr int kMinTableSize = 256; 297 298 if (fn->fSize >= kMinTableSize) { 299 return false; 300 } 301 302 float* outTable = fAlloc.makeArray<float>(kMinTableSize); 303 float step = 1.0f / (kMinTableSize - 1); 304 for (int i = 0; i < kMinTableSize; i++) { 305 outTable[i] = interp_lut(i * step, fn->fData, fn->fSize); 306 } 307 308 fn->fData = outTable; 309 fn->fSize = kMinTableSize; 310 return true; 311 } 312 313 void SkColorSpaceXform_A2B::addTableFn(const SkTableTransferFn& fn, int channelIndex) { 314 switch (channelIndex) { 315 case 0: 316 fElementsPipeline.append(SkRasterPipeline::table_r, this->copy(fn)); 317 break; 318 case 1: 319 fElementsPipeline.append(SkRasterPipeline::table_g, this->copy(fn)); 320 break; 321 case 2: 322 fElementsPipeline.append(SkRasterPipeline::table_b, this->copy(fn)); 323 break; 324 case 3: 325 fElementsPipeline.append(SkRasterPipeline::table_a, this->copy(fn)); 326 break; 327 default: 328 SkASSERT(false); 329 } 330 } 331 332 void SkColorSpaceXform_A2B::addMatrix(const SkMatrix44& m44) { 333 auto m = fAlloc.makeArray<float>(12); 334 m[0] = m44.get(0,0); m[ 1] = m44.get(1,0); m[ 2] = m44.get(2,0); 335 m[3] = m44.get(0,1); m[ 4] = m44.get(1,1); m[ 5] = m44.get(2,1); 336 m[6] = m44.get(0,2); m[ 7] = m44.get(1,2); m[ 8] = m44.get(2,2); 337 m[9] = m44.get(0,3); m[10] = m44.get(1,3); m[11] = m44.get(2,3); 338 339 SkASSERT(m44.get(3,0) == 0.0f); 340 SkASSERT(m44.get(3,1) == 0.0f); 341 SkASSERT(m44.get(3,2) == 0.0f); 342 SkASSERT(m44.get(3,3) == 1.0f); 343 344 fElementsPipeline.append(SkRasterPipeline::matrix_3x4, m); 345 fElementsPipeline.append(SkRasterPipeline::clamp_0); 346 fElementsPipeline.append(SkRasterPipeline::clamp_1); 347 } 348