in modules/skcms/skcms.cc [2561:2883]
bool skcms_Transform(const void* src,
skcms_PixelFormat srcFmt,
skcms_AlphaFormat srcAlpha,
const skcms_ICCProfile* srcProfile,
void* dst,
skcms_PixelFormat dstFmt,
skcms_AlphaFormat dstAlpha,
const skcms_ICCProfile* dstProfile,
size_t nz) {
const size_t dst_bpp = bytes_per_pixel(dstFmt),
src_bpp = bytes_per_pixel(srcFmt);
// Let's just refuse if the request is absurdly big.
if (nz * dst_bpp > INT_MAX || nz * src_bpp > INT_MAX) {
return false;
}
int n = (int)nz;
// Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
if (!srcProfile) {
srcProfile = skcms_sRGB_profile();
}
if (!dstProfile) {
dstProfile = skcms_sRGB_profile();
}
// We can't transform in place unless the PixelFormats are the same size.
if (dst == src && dst_bpp != src_bpp) {
return false;
}
// TODO: more careful alias rejection (like, dst == src + 1)?
Op program[32];
const void* context[32];
Op* ops = program;
const void** contexts = context;
auto add_op = [&](Op o) {
*ops++ = o;
*contexts++ = nullptr;
};
auto add_op_ctx = [&](Op o, const void* c) {
*ops++ = o;
*contexts++ = c;
};
auto add_curve_ops = [&](const skcms_Curve* curves, int numChannels) {
OpAndArg oa[4];
assert(numChannels <= ARRAY_COUNT(oa));
int numOps = select_curve_ops(curves, numChannels, oa);
for (int i = 0; i < numOps; ++i) {
add_op_ctx(oa[i].op, oa[i].arg);
}
};
// These are always parametric curves of some sort.
skcms_Curve dst_curves[3];
dst_curves[0].table_entries =
dst_curves[1].table_entries =
dst_curves[2].table_entries = 0;
skcms_Matrix3x3 from_xyz;
switch (srcFmt >> 1) {
default: return false;
case skcms_PixelFormat_A_8 >> 1: add_op(Op::load_a8); break;
case skcms_PixelFormat_G_8 >> 1: add_op(Op::load_g8); break;
case skcms_PixelFormat_GA_88 >> 1: add_op(Op::load_ga88); break;
case skcms_PixelFormat_ABGR_4444 >> 1: add_op(Op::load_4444); break;
case skcms_PixelFormat_RGB_565 >> 1: add_op(Op::load_565); break;
case skcms_PixelFormat_RGB_888 >> 1: add_op(Op::load_888); break;
case skcms_PixelFormat_RGBA_8888 >> 1: add_op(Op::load_8888); break;
case skcms_PixelFormat_RGBA_1010102 >> 1: add_op(Op::load_1010102); break;
case skcms_PixelFormat_RGB_101010x_XR >> 1: add_op(Op::load_101010x_XR); break;
case skcms_PixelFormat_RGBA_10101010_XR >> 1: add_op(Op::load_10101010_XR); break;
case skcms_PixelFormat_RGB_161616LE >> 1: add_op(Op::load_161616LE); break;
case skcms_PixelFormat_RGBA_16161616LE >> 1: add_op(Op::load_16161616LE); break;
case skcms_PixelFormat_RGB_161616BE >> 1: add_op(Op::load_161616BE); break;
case skcms_PixelFormat_RGBA_16161616BE >> 1: add_op(Op::load_16161616BE); break;
case skcms_PixelFormat_RGB_hhh_Norm >> 1: add_op(Op::load_hhh); break;
case skcms_PixelFormat_RGBA_hhhh_Norm >> 1: add_op(Op::load_hhhh); break;
case skcms_PixelFormat_RGB_hhh >> 1: add_op(Op::load_hhh); break;
case skcms_PixelFormat_RGBA_hhhh >> 1: add_op(Op::load_hhhh); break;
case skcms_PixelFormat_RGB_fff >> 1: add_op(Op::load_fff); break;
case skcms_PixelFormat_RGBA_ffff >> 1: add_op(Op::load_ffff); break;
case skcms_PixelFormat_RGBA_8888_sRGB >> 1:
add_op(Op::load_8888);
add_op_ctx(Op::tf_rgb, skcms_sRGB_TransferFunction());
break;
}
if (srcFmt == skcms_PixelFormat_RGB_hhh_Norm ||
srcFmt == skcms_PixelFormat_RGBA_hhhh_Norm) {
add_op(Op::clamp);
}
if (srcFmt & 1) {
add_op(Op::swap_rb);
}
skcms_ICCProfile gray_dst_profile;
switch (dstFmt >> 1) {
case skcms_PixelFormat_G_8:
case skcms_PixelFormat_GA_88:
// When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
// luminance (Y) by the destination transfer function.
gray_dst_profile = *dstProfile;
skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
dstProfile = &gray_dst_profile;
break;
default:
break;
}
if (srcProfile->data_color_space == skcms_Signature_CMYK) {
// Photoshop creates CMYK images as inverse CMYK.
// These happen to be the only ones we've _ever_ seen.
add_op(Op::invert);
// With CMYK, ignore the alpha type, to avoid changing K or conflating CMY with K.
srcAlpha = skcms_AlphaFormat_Unpremul;
}
if (srcAlpha == skcms_AlphaFormat_Opaque) {
add_op(Op::force_opaque);
} else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
add_op(Op::unpremul);
}
if (dstProfile != srcProfile) {
if (!prep_for_destination(dstProfile,
&from_xyz,
&dst_curves[0].parametric,
&dst_curves[1].parametric,
&dst_curves[2].parametric)) {
return false;
}
if (srcProfile->has_A2B) {
if (srcProfile->A2B.input_channels) {
add_curve_ops(srcProfile->A2B.input_curves,
(int)srcProfile->A2B.input_channels);
add_op(Op::clamp);
add_op_ctx(Op::clut_A2B, &srcProfile->A2B);
}
if (srcProfile->A2B.matrix_channels == 3) {
add_curve_ops(srcProfile->A2B.matrix_curves, /*numChannels=*/3);
static const skcms_Matrix3x4 I = {{
{1,0,0,0},
{0,1,0,0},
{0,0,1,0},
}};
if (0 != memcmp(&I, &srcProfile->A2B.matrix, sizeof(I))) {
add_op_ctx(Op::matrix_3x4, &srcProfile->A2B.matrix);
}
}
if (srcProfile->A2B.output_channels == 3) {
add_curve_ops(srcProfile->A2B.output_curves, /*numChannels=*/3);
}
if (srcProfile->pcs == skcms_Signature_Lab) {
add_op(Op::lab_to_xyz);
}
} else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
add_curve_ops(srcProfile->trc, /*numChannels=*/3);
} else {
return false;
}
// A2B sources are in XYZD50 by now, but TRC sources are still in their original gamut.
assert (srcProfile->has_A2B || srcProfile->has_toXYZD50);
if (dstProfile->has_B2A) {
// B2A needs its input in XYZD50, so transform TRC sources now.
if (!srcProfile->has_A2B) {
add_op_ctx(Op::matrix_3x3, &srcProfile->toXYZD50);
}
if (dstProfile->pcs == skcms_Signature_Lab) {
add_op(Op::xyz_to_lab);
}
if (dstProfile->B2A.input_channels == 3) {
add_curve_ops(dstProfile->B2A.input_curves, /*numChannels=*/3);
}
if (dstProfile->B2A.matrix_channels == 3) {
static const skcms_Matrix3x4 I = {{
{1,0,0,0},
{0,1,0,0},
{0,0,1,0},
}};
if (0 != memcmp(&I, &dstProfile->B2A.matrix, sizeof(I))) {
add_op_ctx(Op::matrix_3x4, &dstProfile->B2A.matrix);
}
add_curve_ops(dstProfile->B2A.matrix_curves, /*numChannels=*/3);
}
if (dstProfile->B2A.output_channels) {
add_op(Op::clamp);
add_op_ctx(Op::clut_B2A, &dstProfile->B2A);
add_curve_ops(dstProfile->B2A.output_curves,
(int)dstProfile->B2A.output_channels);
}
} else {
// This is a TRC destination.
// We'll concat any src->xyz matrix with our xyz->dst matrix into one src->dst matrix.
// (A2B sources are already in XYZD50, making that src->xyz matrix I.)
static const skcms_Matrix3x3 I = {{
{ 1.0f, 0.0f, 0.0f },
{ 0.0f, 1.0f, 0.0f },
{ 0.0f, 0.0f, 1.0f },
}};
const skcms_Matrix3x3* to_xyz = srcProfile->has_A2B ? &I : &srcProfile->toXYZD50;
// There's a chance the source and destination gamuts are identical,
// in which case we can skip the gamut transform.
if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
// Concat the entire gamut transform into from_xyz,
// now slightly misnamed but it's a handy spot to stash the result.
from_xyz = skcms_Matrix3x3_concat(&from_xyz, to_xyz);
add_op_ctx(Op::matrix_3x3, &from_xyz);
}
// Encode back to dst RGB using its parametric transfer functions.
OpAndArg oa[3];
int numOps = select_curve_ops(dst_curves, /*numChannels=*/3, oa);
for (int index = 0; index < numOps; ++index) {
assert(oa[index].op != Op::table_r &&
oa[index].op != Op::table_g &&
oa[index].op != Op::table_b &&
oa[index].op != Op::table_a);
add_op_ctx(oa[index].op, oa[index].arg);
}
}
}
// Clamp here before premul to make sure we're clamping to normalized values _and_ gamut,
// not just to values that fit in [0,1].
//
// E.g. r = 1.1, a = 0.5 would fit fine in fixed point after premul (ra=0.55,a=0.5),
// but would be carrying r > 1, which is really unexpected for downstream consumers.
if (dstFmt < skcms_PixelFormat_RGB_hhh) {
add_op(Op::clamp);
}
if (dstProfile->data_color_space == skcms_Signature_CMYK) {
// Photoshop creates CMYK images as inverse CMYK.
// These happen to be the only ones we've _ever_ seen.
add_op(Op::invert);
// CMYK has no alpha channel, so make sure dstAlpha is a no-op.
dstAlpha = skcms_AlphaFormat_Unpremul;
}
if (dstAlpha == skcms_AlphaFormat_Opaque) {
add_op(Op::force_opaque);
} else if (dstAlpha == skcms_AlphaFormat_PremulAsEncoded) {
add_op(Op::premul);
}
if (dstFmt & 1) {
add_op(Op::swap_rb);
}
switch (dstFmt >> 1) {
default: return false;
case skcms_PixelFormat_A_8 >> 1: add_op(Op::store_a8); break;
case skcms_PixelFormat_G_8 >> 1: add_op(Op::store_g8); break;
case skcms_PixelFormat_GA_88 >> 1: add_op(Op::store_ga88); break;
case skcms_PixelFormat_ABGR_4444 >> 1: add_op(Op::store_4444); break;
case skcms_PixelFormat_RGB_565 >> 1: add_op(Op::store_565); break;
case skcms_PixelFormat_RGB_888 >> 1: add_op(Op::store_888); break;
case skcms_PixelFormat_RGBA_8888 >> 1: add_op(Op::store_8888); break;
case skcms_PixelFormat_RGBA_1010102 >> 1: add_op(Op::store_1010102); break;
case skcms_PixelFormat_RGB_161616LE >> 1: add_op(Op::store_161616LE); break;
case skcms_PixelFormat_RGBA_16161616LE >> 1: add_op(Op::store_16161616LE); break;
case skcms_PixelFormat_RGB_161616BE >> 1: add_op(Op::store_161616BE); break;
case skcms_PixelFormat_RGBA_16161616BE >> 1: add_op(Op::store_16161616BE); break;
case skcms_PixelFormat_RGB_hhh_Norm >> 1: add_op(Op::store_hhh); break;
case skcms_PixelFormat_RGBA_hhhh_Norm >> 1: add_op(Op::store_hhhh); break;
case skcms_PixelFormat_RGB_101010x_XR >> 1: add_op(Op::store_101010x_XR); break;
case skcms_PixelFormat_RGBA_10101010_XR >> 1: add_op(Op::store_10101010_XR); break;
case skcms_PixelFormat_RGB_hhh >> 1: add_op(Op::store_hhh); break;
case skcms_PixelFormat_RGBA_hhhh >> 1: add_op(Op::store_hhhh); break;
case skcms_PixelFormat_RGB_fff >> 1: add_op(Op::store_fff); break;
case skcms_PixelFormat_RGBA_ffff >> 1: add_op(Op::store_ffff); break;
case skcms_PixelFormat_RGBA_8888_sRGB >> 1:
add_op_ctx(Op::tf_rgb, skcms_sRGB_Inverse_TransferFunction());
add_op(Op::store_8888);
break;
}
assert(ops <= program + ARRAY_COUNT(program));
assert(contexts <= context + ARRAY_COUNT(context));
auto run = baseline::run_program;
switch (cpu_type()) {
case CpuType::SKX:
#if !defined(SKCMS_DISABLE_SKX)
run = skx::run_program;
break;
#endif
case CpuType::HSW:
#if !defined(SKCMS_DISABLE_HSW)
run = hsw::run_program;
break;
#endif
case CpuType::Baseline:
break;
}
run(program, context, ops - program, (const char*)src, (char*)dst, n, src_bpp,dst_bpp);
return true;
}