org.apache.commons.imaging.formats.jpeg.decoder.YCbCrConverter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of commons-imaging Show documentation
Show all versions of commons-imaging Show documentation
Apache Commons Imaging (previously Sanselan) is a pure-Java image library.
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* under the License.
*/
package org.apache.commons.imaging.formats.jpeg.decoder;
final class YCbCrConverter {
private static final int[] REDS = new int[256 * 256];
private static final int[] BLUES = new int[256 * 256];
private static final int[] GREENS1 = new int[256 * 256];
private static final int[] GREENS2 = new int[256 * 512];
static {
/*
* Why use (Cr << 8) | Y and not (Y << 8) | Cr as the index? Y changes
* often, while Cb and Cr is usually subsampled less often and repeats
* itself between adjacent pixels, so using it as the high order byte
* gives higher locality of reference.
*/
for (int Y = 0; Y < 256; Y++) {
for (int Cr = 0; Cr < 256; Cr++) {
int r = Y + fastRound(1.402f * (Cr - 128));
if (r < 0) {
r = 0;
}
if (r > 255) {
r = 255;
}
REDS[(Cr << 8) | Y] = r << 16;
}
}
for (int Y = 0; Y < 256; Y++) {
for (int Cb = 0; Cb < 256; Cb++) {
int b = Y + fastRound(1.772f * (Cb - 128));
if (b < 0) {
b = 0;
}
if (b > 255) {
b = 255;
}
BLUES[(Cb << 8) | Y] = b;
}
}
// green is the hardest
// Math.round((float) (Y - 0.34414*(Cb-128) - 0.71414*(Cr-128)))
// but Y is integral
// = Y - Math.round((float) (0.34414*(Cb-128) + 0.71414*(Cr-128)))
// = Y - Math.round(f(Cb, Cr))
// where
// f(Cb, Cr) = 0.34414*(Cb-128) + 0.71414*(Cr-128)
// Cb and Cr terms each vary from 255-128 = 127 to 0-128 = -128
// Linear function, so only examine endpoints:
// Cb term Cr term Result
// 127 127 134.4
// -128 -128 -135.4
// 127 -128 -47.7
// -128 127 46.6
// Thus with -135 being the minimum and 134 the maximum,
// there is a range of 269 values,
// and 135 needs to be added to make it zero-based.
// As for Y - f(Cb, Cr)
// the range becomes:
// Y f(Cb, Cr)
// 255 -135
// 255 134
// 0 -135
// 0 134
// thus the range is [-134,390] and has 524 values
// but is clamped to [0, 255]
for (int Cb = 0; Cb < 256; Cb++) {
for (int Cr = 0; Cr < 256; Cr++) {
final int value = fastRound(0.34414f * (Cb - 128) + 0.71414f
* (Cr - 128));
GREENS1[(Cb << 8) | Cr] = value + 135;
}
}
for (int Y = 0; Y < 256; Y++) {
for (int value = 0; value < 270; value++) {
int green = Y - (value - 135);
if (green < 0) {
green = 0;
} else if (green > 255) {
green = 255;
}
GREENS2[(value << 8) | Y] = green << 8;
}
}
}
private YCbCrConverter() {
}
private static int fastRound(final float x) {
// Math.round() is very slow
return (int) (x + 0.5f);
}
public static int convertYCbCrToRGB(final int Y, final int Cb, final int Cr) {
final int r = REDS[(Cr << 8) | Y];
final int g1 = GREENS1[(Cb << 8) | Cr];
final int g = GREENS2[(g1 << 8) | Y];
final int b = BLUES[(Cb << 8) | Y];
return r | g | b;
}
}