mxwcore-legion/dep/g3dlite/source/GImage.cpp

1167 lines
30 KiB
C++

/**
\file GImage.cpp
\author Morgan McGuire, http://graphics.cs.williams.edu
Copyright 2002-2010, Morgan McGuire
\created 2002-05-27
\edited 2010-01-04
*/
#include "G3D/platform.h"
#include "G3D/GImage.h"
#include "G3D/debug.h"
#include "G3D/stringutils.h"
#include "G3D/TextInput.h"
#include "G3D/TextOutput.h"
#include "G3D/BinaryInput.h"
#include "G3D/BinaryOutput.h"
#include "G3D/Log.h"
#include "G3D/fileutils.h"
#ifdef G3D_LINUX
# include <png.h>
#else
# include "png.h"
#endif
#include <sys/stat.h>
#include <assert.h>
#include <sys/types.h>
//////////////////////////////////////////////////////////////////////////////////////////////
namespace G3D {
void GImage::LtoRGBA(
const uint8* in,
uint8* out,
int numPixels) {
for (int i = 0; i < numPixels; ++i) {
int v = in[i];
int i4 = i * 4;
out[i4 + 0] = v;
out[i4 + 1] = v;
out[i4 + 2] = v;
out[i4 + 3] = 255;
}
}
void GImage::LtoRGB(
const uint8* in,
uint8* out,
int numPixels) {
for (int i = 0; i < numPixels; ++i) {
int v = in[i];
int i3 = i * 3;
out[i3 + 0] = v;
out[i3 + 1] = v;
out[i3 + 2] = v;
}
}
void GImage::RGBtoRGBA(
const uint8* in,
uint8* out,
int numPixels) {
for (int i = 0; i < numPixels; ++i) {
int i3 = i * 3;
int i4 = i3 + i;
out[i4 + 0] = in[i3 + 0];
out[i4 + 1] = in[i3 + 1];
out[i4 + 2] = in[i3 + 2];
out[i4 + 3] = 255;
}
}
void GImage::RGBAtoRGB(
const uint8* in,
uint8* out,
int numPixels) {
for (int i = 0; i < numPixels; ++i) {
int i3 = i * 3;
int i4 = i3 + i;
out[i3 + 0] = in[i4 + 0];
out[i3 + 1] = in[i4 + 1];
out[i3 + 2] = in[i4 + 2];
}
}
void GImage::RGBtoBGRA(
const uint8* in,
uint8* out,
int numPixels) {
for (int i = 0; i < numPixels; ++i) {
int i3 = i * 3;
int i4 = i3 + i;
out[i4 + 2] = in[i3 + 0];
out[i4 + 1] = in[i3 + 1];
out[i4 + 0] = in[i3 + 2];
out[i4 + 3] = 255;
}
}
void GImage::RGBtoBGR(
const uint8* in,
uint8* out,
int numPixels) {
for (int i = 0; i < numPixels; ++i) {
int i3 = i * 3;
int r = in[i3 + 0];
int g = in[i3 + 1];
int b = in[i3 + 2];
out[i3 + 2] = r;
out[i3 + 1] = g;
out[i3 + 0] = b;
}
}
void GImage::RGBxRGBtoRGBA(
const uint8* colorRGB,
const uint8* alphaRGB,
uint8* out,
int numPixels) {
for (int i = numPixels - 1; i >= 0; --i) {
int i3 = i * 3;
int i4 = i3 + i;
out[i4 + 0] = colorRGB[i3 + 0];
out[i4 + 1] = colorRGB[i3 + 1];
out[i4 + 2] = colorRGB[i3 + 2];
out[i4 + 3] = alphaRGB[i3 + 0];
}
}
void GImage::RGBtoARGB(
const uint8* in,
uint8* out,
int numPixels) {
for (int i = 0; i < numPixels; ++i) {
int i3 = i * 3;
int i4 = i3 + i;
out[i4 + 0] = 255;
out[i4 + 1] = in[i3 + 0];
out[i4 + 2] = in[i3 + 1];
out[i4 + 3] = in[i3 + 2];
}
}
void GImage::flipRGBVertical(
const uint8* in,
uint8* out,
int width,
int height) {
// Allocate a temp row so the operation
// is still safe if in == out
uint8* temp = (uint8*)System::malloc(width * 3);
alwaysAssertM(temp != NULL, "Out of memory");
int oneRow = width * 3;
int N = height / 2;
// if height is an odd value, don't swap odd middle row
for (int i = 0; i < N; ++i) {
int topOff = i * oneRow;
int botOff = (height - i - 1) * oneRow;
System::memcpy(temp, in + topOff, oneRow);
System::memcpy(out + topOff, in + botOff, oneRow);
System::memcpy(out + botOff, temp, oneRow);
}
System::free(temp);
}
void GImage::flipRGBAVertical(
const uint8* in,
uint8* out,
int width,
int height) {
// Allocate a temp row so the operation
// is still safe if in == out
uint8* temp = (uint8*)System::malloc(width * 4);
alwaysAssertM(temp != NULL, "Out of memory");
int oneRow = width * 4;
// if height is an odd value, don't swap odd middle row
for (int i = 0; i < height / 2; ++i) {
int topOff = i * oneRow;
int botOff = (height - i - 1) * oneRow;
System::memcpy(temp, in + topOff, oneRow);
System::memcpy(out + topOff, in + botOff, oneRow);
System::memcpy(out + botOff, temp, oneRow);
}
System::free(temp);
}
////////////////////////////////////////////////////////////////////////////////////////
void GImage::decode(
BinaryInput& input,
Format format) {
switch (format) {
case PPM_ASCII:
decodePPMASCII(input);
break;
case PPM_BINARY:
decodePPM(input);
break;
case PNG:
decodePNG(input);
break;
case JPEG:
decodeJPEG(input);
break;
case TGA:
decodeTGA(input);
break;
case BMP:
decodeBMP(input);
break;
case ICO:
decodeICO(input);
break;
case PCX:
decodePCX(input);
break;
default:
debugAssert(false);
}
debugAssert(m_width >= 0);
debugAssert(m_height >= 0);
debugAssert(m_channels == 1 || m_channels == 3 || m_channels == 4);
debugAssert(m_byte != NULL);
}
void GImage::decodePCX(
BinaryInput& input) {
uint8 manufacturer = input.readUInt8();
uint8 version = input.readUInt8();
uint8 encoding = input.readUInt8();
uint8 bitsPerPixel = input.readUInt8();
uint16 xmin = input.readUInt16();
uint16 ymin = input.readUInt16();
uint16 xmax = input.readUInt16();
uint16 ymax = input.readUInt16();
uint16 horizDPI = input.readUInt16();
uint16 vertDPI = input.readUInt16();
Color3uint8 colorMap[16];
input.readBytes(colorMap, 48);
input.skip(1);
uint8 planes = input.readUInt8();
uint16 bytesPerLine = input.readUInt16();
uint16 paletteType = input.readUInt16();
input.skip(4 + 54);
(void)bytesPerLine;
m_width = xmax - xmin + 1;
m_height = ymax - ymin + 1;
m_channels = 3;
if ((manufacturer != 0x0A) || (encoding != 0x01)) {
throw GImage::Error("PCX file is corrupted", input.getFilename());
}
(void)version;
(void)vertDPI;
(void)horizDPI;
if ((bitsPerPixel != 8) || ((planes != 1) && (planes != 3))) {
throw GImage::Error("Only 8-bit paletted and 24-bit PCX files supported.", input.getFilename());
}
// Prepare the pointer object for the pixel data
m_byte = (uint8*)m_memMan->alloc(m_width * m_height * 3);
if ((paletteType == 1) && (planes == 3)) {
Color3uint8* pixel = pixel3();
// Iterate over each scan line
for (int row = 0; row < m_height; ++row) {
// Read each scan line once per plane
for (int plane = 0; plane < planes; ++plane) {
int p = row * m_width;
int p1 = p + m_width;
while (p < p1) {
uint8 value = input.readUInt8();
int length = 1;
if (value >= 192) {
// This is the length, not the value. Mask off
// the two high bits and read the true index.
length = value & 0x3F;
value = input.readUInt8();
}
// Set the whole run
for (int i = length - 1; i >= 0; --i, ++p) {
debugAssert(p < m_width * m_height);
pixel[p][plane] = value;
}
}
}
}
} else if (planes == 1) {
Color3uint8 palette[256];
int imageBeginning = input.getPosition();
int paletteBeginning = input.getLength() - 769;
input.setPosition(paletteBeginning);
uint8 dummy = input.readUInt8();
if (dummy != 12) {
Log::common()->println("\n*********************");
Log::common()->printf("Warning: Corrupted PCX file (palette marker byte was missing) \"%s\"\nLoading anyway\n\n", input.getFilename().c_str());
}
input.readBytes(palette, sizeof(palette));
input.setPosition(imageBeginning);
Color3uint8* pixel = pixel3();
// The palette indices are run length encoded.
int p = 0;
while (p < m_width * m_height) {
uint8 index = input.readUInt8();
uint8 length = 1;
if (index >= 192) {
// This is the length, not the index. Mask off
// the two high bits and read the true index.
length = index & 0x3F;
index = input.readUInt8();
}
Color3uint8 color = palette[index];
// Set the whole run
for (int i = length - 1; i >= 0; --i, ++p) {
if (p > m_width * m_height) {
break;
}
pixel[p] = color;
}
}
} else {
throw GImage::Error("Unsupported PCX file type.", input.getFilename());
}
}
GImage::Format GImage::resolveFormat(const std::string& filename) {
BinaryInput b(filename, G3D_LITTLE_ENDIAN);
if (b.size() <= 0) {
throw Error("File not found.", filename);
}
return resolveFormat(filename, b.getCArray(), b.size(), AUTODETECT);
}
GImage::Format GImage::resolveFormat(
const std::string& filename,
const uint8* data,
int dataLen,
Format maybeFormat) {
// Return the provided format if it is specified.
if (maybeFormat != AUTODETECT) {
return maybeFormat;
}
std::string extension = toUpper(filenameExt(filename));
if ((extension == "PPM") || (extension == "PGM") || (extension == "PBM")) {
// There are two PPM formats (binary and ASCII); we handle them differently
if (dataLen > 3) {
if (!memcmp(data, "P6", 2) || !memcmp(data, "P5", 2)) {
return PPM_BINARY;
} else {
return PPM_ASCII;
}
}
}
Format tmp = stringToFormat(extension);
if ((tmp != AUTODETECT) && (tmp != UNKNOWN)) {
return tmp;
}
// Try and autodetect from the file itself by looking at the first
// character.
// We can't look at the character if it is null.
debugAssert(data != NULL);
if ((dataLen > 3) && (! memcmp(data, "P3", 2) || (! memcmp(data, "P2", 2)) || (! memcmp(data, "P1", 2)))) {
return PPM_ASCII;
}
if ((dataLen > 3) && (!memcmp(data, "P6", 2) ||!memcmp(data, "P5", 2))) {
return PPM_BINARY;
}
if (dataLen > 8) {
if (!png_sig_cmp((png_bytep)data, 0, 8)) {
return PNG;
}
}
if ((dataLen > 0) && (data[0] == 'B')) {
return BMP;
}
if (dataLen > 10) {
if ((dataLen > 11) && (data[0] == 0xFF) &&
(memcmp(&data[6], "JFIF", 4) == 0)) {
return JPEG;
}
}
if (dataLen > 40) {
if (memcmp(&data[dataLen - 18], "TRUEVISION-XFILE", 16) == 0) {
return TGA;
}
}
if ((dataLen > 4) && (data[0] == 0) && (data[1] == 0) && (data[2] == 0) && (data[3] == 1)) {
return ICO;
}
if ((dataLen > 0) && (data[0] == 10)) {
return PCX;
}
return UNKNOWN;
}
GImage::GImage(
const std::string& filename,
Format format,
const MemoryManager::Ref& m) :
m_memMan(m),
m_byte(NULL),
m_channels(0),
m_width(0),
m_height(0) {
load(filename, format);
}
void GImage::load(
const std::string& filename,
Format format) {
clear();
try {
BinaryInput b(filename, G3D_LITTLE_ENDIAN);
if (b.size() <= 0) {
throw Error("File not found.", filename);
}
alwaysAssertM(this != NULL, "Corrupt GImage");
decode(b, resolveFormat(filename, b.getCArray(), b.size(), format));
} catch (const std::string& error) {
throw Error(error, filename);
}
}
GImage::GImage(
const uint8* data,
int length,
Format format,
const MemoryManager::Ref& m) :
m_memMan(m),
m_byte(NULL),
m_channels(0),
m_width(0),
m_height(0) {
BinaryInput b(data, length, G3D_LITTLE_ENDIAN);
// It is safe to cast away the const because we
// know we don't corrupt the data.
decode(b, resolveFormat("", data, length, format));
}
GImage::GImage(
int width,
int height,
int channels,
const MemoryManager::Ref& mem) :
m_memMan(mem),
m_byte(0),
m_channels(0),
m_width(0),
m_height(0) {
resize(width, height, channels);
}
void GImage::resize(
int width,
int height,
int channels,
bool zero) {
debugAssert(width >= 0);
debugAssert(height >= 0);
debugAssert(channels >= 1);
clear();
m_width = width;
m_height = height;
m_channels = channels;
size_t sz = width * height * channels;
if (sz > 0) {
m_byte = (uint8*)m_memMan->alloc(sz);
if (zero) {
System::memset(m_byte, 0, sz);
}
debugAssert(isValidHeapPointer(m_byte));
}
}
void GImage::_copy(
const GImage& other) {
clear();
m_width = other.m_width;
m_height = other.m_height;
m_channels = other.m_channels;
int s = m_width * m_height * m_channels * sizeof(uint8);
m_byte = (uint8*)m_memMan->alloc(s);
debugAssert(isValidHeapPointer(m_byte));
memcpy(m_byte, other.m_byte, s);
}
void GImage::flipHorizontal() {
uint8 temp[4];
int rowBytes = m_width * m_channels;
for (int y = 0; y < m_height; ++y) {
uint8* row = m_byte + y * rowBytes;
for (int x = 0; x < m_width / 2; ++x) {
System::memcpy(temp, row + x * m_channels, m_channels);
System::memcpy(row + x * m_channels, row + (m_width - x - 1) * m_channels, m_channels);
System::memcpy(row + (m_width - x - 1) * m_channels, temp, m_channels);
}
}
}
void GImage::flipVertical() {
uint8* old = m_byte;
m_byte = (uint8*)m_memMan->alloc(m_width * m_height * m_channels);
// We could do this with only a single-row temp buffer, but then
// we'd have to copy twice as much data.
int rowBytes = m_width * m_channels;
for (int y = 0; y < m_height; ++y) {
System::memcpy(m_byte + y * rowBytes, old + (m_height - y - 1) * rowBytes, rowBytes);
}
m_memMan->free(old);
}
void GImage::rotate90CW(int numTimes) {
uint8* old = NULL;
numTimes = iWrap(numTimes, 4);
if (numTimes > 0) {
(uint8*)m_memMan->alloc(m_width * m_height * m_channels);
}
for (int j = 0; j < numTimes; ++j) {
{
uint8* temp = old;
old = m_byte;
m_byte = temp;
}
{
int temp = m_width;
m_width = m_height;
m_height = temp;
}
int rowBytes = m_width * m_channels;
for (int y = 0; y < m_height; ++y) {
for (int x = 0; x < m_width; ++x) {
uint8* dst = m_byte + x + y * rowBytes;
uint8* src = old + y + (m_height - x - 1) * rowBytes;
System::memcpy(dst, src, m_channels);
}
}
}
m_memMan->free(old);
}
GImage::GImage(
const GImage& other,
const MemoryManager::Ref& m) : m_memMan(m), m_byte(NULL) {
_copy(other);
}
GImage::~GImage() {
clear();
}
void GImage::clear() {
m_width = 0;
m_height = 0;
m_memMan->free(m_byte);
m_byte = NULL;
}
GImage& GImage::operator=(const GImage& other) {
_copy(other);
return *this;
}
bool GImage::copySubImage(
GImage & dest, const GImage & src,
int srcX, int srcY, int srcWidth, int srcHeight) {
if ((src.m_width < srcX + srcWidth) ||
(src.m_height < srcY + srcHeight) ||
(srcY < 0) ||
(srcX < 0)) {
return false;
}
dest.resize(srcWidth, srcHeight, src.m_channels);
bool ret;
ret = pasteSubImage(dest, src, 0, 0, srcX, srcY, srcWidth, srcHeight);
debugAssert(ret);
return true;
}
bool GImage::pasteSubImage(
GImage & dest, const GImage & src,
int destX, int destY,
int srcX, int srcY, int srcWidth, int srcHeight) {
if ((src.m_width < srcX + srcWidth) ||
(src.m_height < srcY + srcHeight) ||
(dest.m_width < destX + srcWidth) ||
(dest.m_height < destY + srcHeight) ||
(srcY < 0) ||
(srcX < 0) ||
(destY < 0) ||
(destX < 0) ||
(src.channels() != dest.channels())) {
return false;
}
for (int i = 0; i < srcHeight; i++) {
const uint8* srcRow = src.byte() +
((i + srcY) * src.m_width + srcX) * src.channels();
uint8* destRow = dest.byte() +
((i + destY) * dest.width() + destX) * dest.channels();
memcpy(destRow, srcRow, srcWidth * src.m_channels);
}
return true;
}
bool GImage::supportedFormat(
const std::string& format) {
return (stringToFormat(format) != UNKNOWN);
}
GImage::Format GImage::stringToFormat(
const std::string& format) {
std::string extension = toUpper(format);
if ((extension == "JPG") || (extension == "JPEG")) {
return JPEG;
} else if (extension == "TGA") {
return TGA;
} else if (extension == "BMP") {
return BMP;
} else if (extension == "PCX") {
return PCX;
} else if (extension == "ICO") {
return ICO;
} else if (extension == "PNG") {
return PNG;
} else {
return UNKNOWN;
}
// Don't put PPM here, since it has two versions
}
void GImage::save(
const std::string& filename,
Format format) const {
BinaryOutput b(filename, G3D_LITTLE_ENDIAN);
encode(resolveFormat(filename, NULL, 0, format), b);
b.commit(false);
}
void GImage::encode(
Format format,
uint8*& outData,
int& outLength) const {
BinaryOutput out;
encode(format, out);
outData = (uint8*)System::malloc(out.size());
debugAssert(outData);
outLength = out.size();
out.commit(outData);
}
void GImage::encode(
Format format,
BinaryOutput& out) const {
switch (format) {
case PPM_ASCII:
encodePPMASCII(out);
break;
case PPM_BINARY:
encodePPM(out);
break;
case PNG:
encodePNG(out);
break;
case JPEG:
encodeJPEG(out);
break;
case BMP:
encodeBMP(out);
break;
case TGA:
encodeTGA(out);
break;
default:
debugAssert(false);
}
}
void GImage::insertRedAsAlpha(const GImage& alpha, GImage& output) const {
debugAssert(alpha.width() == width());
debugAssert(alpha.height() == height());
// make sure output GImage is valid
if (output.width() != width() || output.height() != height() || output.channels() != 4) {
output.resize(width(), height(), 4);
}
int N = m_width * m_height;
for (int i = 0; i < N; ++i) {
output.byte()[i * 4 + 0] = byte()[i * m_channels + 0];
output.byte()[i * 4 + 1] = byte()[i * m_channels + 1];
output.byte()[i * 4 + 2] = byte()[i * m_channels + 2];
output.byte()[i * 4 + 3] = alpha.byte()[i * alpha.m_channels];
}
}
void GImage::stripAlpha(GImage& output) const {
if (output.m_width != m_width || output.m_height != m_height || output.m_channels != 3) {
output.resize(m_width, m_height, 3);
}
int N = m_width * m_height;
for (int i = 0; i < N; ++i) {
output.byte()[i * 3 + 0] = byte()[i * m_channels + 0];
output.byte()[i * 3 + 1] = byte()[i * m_channels + 1];
output.byte()[i * 3 + 2] = byte()[i * m_channels + 2];
}
}
int GImage::sizeInMemory() const {
return sizeof(GImage) + m_width * m_height * m_channels;
}
void GImage::computeNormalMap(
const GImage& bump,
GImage& normal,
const BumpMapPreprocess& preprocess) {
computeNormalMap(bump.m_width, bump.m_height, bump.m_channels,
bump.byte(), normal, preprocess);
}
void GImage::computeNormalMap(
int width,
int height,
int channels,
const uint8* src,
GImage& normal,
const BumpMapPreprocess& preprocess) {
float whiteHeightInPixels = preprocess.zExtentPixels;
bool lowPassBump = preprocess.lowPassFilter;
bool scaleHeightByNz = preprocess.scaleZByNz;
if (whiteHeightInPixels < 0.0f) {
// Default setting scales so that a gradient ramp
// over the whole image becomes a 45-degree angle
// Account for potentially non-square aspect ratios
whiteHeightInPixels = max(width, height) * -whiteHeightInPixels;
}
debugAssert(whiteHeightInPixels >= 0);
const int w = width;
const int h = height;
const int stride = channels;
normal.resize(w, h, 4);
const uint8* const B = src;
Color4uint8* const N = normal.pixel4();
// 1/s for the scale factor that each ELEVATION should be multiplied by.
// We avoid actually multiplying by this and instead just divide it out of z.
float elevationInvScale = 255.0f / whiteHeightInPixels;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
// Index into normal map pixel
int i = x + y * w;
// Index into bump map *byte*
int j = stride * i;
Vector3 delta;
// Get a value from B (with wrapping lookup) relative to (x, y)
// and divide by 255
#define ELEVATION(DX, DY) ((int)B[(((DX + x + w) % w) + \
((DY + y + h) % h) * w) * stride])
// Sobel filter to compute the normal.
//
// Y Filter (X filter is the transpose)
// [ -1 -2 -1 ]
// [ 0 0 0 ]
// [ 1 2 1 ]
// Write the Y value directly into the x-component so we don't have
// to explicitly compute a cross product at the end. Does not
// go out of bounds because the above is computed mod (width, height)
delta.y = -( ELEVATION(-1, -1) * 1 + ELEVATION( 0, -1) * 2 + ELEVATION( 1, -1) * 1 +
-ELEVATION(-1, 1) * 1 + -ELEVATION( 0, 1) * 2 + -ELEVATION( 1, 1) * 1);
delta.x = -(-ELEVATION(-1, -1) * 1 + ELEVATION( 1, -1) * 1 +
-ELEVATION(-1, 0) * 2 + ELEVATION( 1, 0) * 2 +
-ELEVATION(-1, 1) * 1 + ELEVATION( 1, 1) * 1);
// The scale of each filter row is 4, the filter width is two pixels,
// and the "normal" range is 0-255.
delta.z = 4 * 2 * elevationInvScale;
// Delta is now scaled in pixels; normalize
delta = delta.direction();
// Copy over the bump value into the alpha channel.
float H = B[j] / 255.0f;
if (lowPassBump) {
H = (ELEVATION(-1, -1) + ELEVATION( 0, -1) + ELEVATION(1, -1) +
ELEVATION(-1, 0) + ELEVATION( 0, 0) + ELEVATION(1, 0) +
ELEVATION(-1, 1) + ELEVATION( 0, 1) + ELEVATION(1, 1)) / (255.0f * 9.0f);
}
# undef ELEVATION
if (scaleHeightByNz) {
// delta.z can't possibly be negative, so we avoid actually
// computing the absolute value.
H *= delta.z;
}
N[i].a = iRound(H * 255.0f);
// Pack into byte range
delta = delta * 127.5f + Vector3(127.5f, 127.5f, 127.5f);
N[i].r = iClamp(iRound(delta.x), 0, 255);
N[i].g = iClamp(iRound(delta.y), 0, 255);
N[i].b = iClamp(iRound(delta.z), 0, 255);
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void GImage::convertToL8() {
switch (m_channels) {
case 1:
return;
case 3:
{
// Average
Color3uint8* src = (Color3uint8*)m_byte;
m_byte = NULL;
resize(m_width, m_height, 1);
for (int i = m_width * m_height - 1; i >= 0; --i) {
const Color3uint8 s = src[i];
uint8& d = m_byte[i];
d = ((int)s.r + (int)s.g + (int)s.b) / 3;
}
m_memMan->free(src);
}
break;
case 4:
{
// Average
Color4uint8* src = (Color4uint8*)m_byte;
m_byte = NULL;
resize(m_width, m_height, 1);
for (int i = m_width * m_height - 1; i >= 0; --i) {
const Color4uint8 s = src[i];
uint8& d = m_byte[i];
d = ((int)s.r + (int)s.g + (int)s.b) / 3;
}
m_memMan->free(src);
}
return;
default:
alwaysAssertM(false, "Bad number of channels in input image");
}
}
void GImage::convertToRGBA() {
switch (m_channels) {
case 1:
{
// Spread
uint8* old = m_byte;
m_byte = NULL;
resize(m_width, m_height, 4);
for (int i = m_width * m_height - 1; i >= 0; --i) {
const uint8 s = old[i];
Color4uint8& d = ((Color4uint8*)m_byte)[i];
d.r = d.g = d.b = s;
d.a = 255;
}
m_memMan->free(m_byte);
}
break;
case 3:
{
// Add alpha
Color3uint8* old = (Color3uint8*)m_byte;
m_byte = NULL;
resize(m_width, m_height, 4);
for (int i = m_width * m_height - 1; i >= 0; --i) {
const Color3uint8 s = old[i];
Color4uint8& d = ((Color4uint8*)m_byte)[i];
d.r = s.r;
d.g = s.g;
d.b = s.b;
d.a = 255;
}
m_memMan->free(old);
}
break;
case 4:
// Already RGBA
return;
default:
alwaysAssertM(false, "Bad number of channels in input image");
}
}
void GImage::convertToRGB() {
switch (m_channels) {
case 1:
{
// Spread
uint8* old = m_byte;
m_byte = NULL;
resize(m_width, m_height, 3);
for (int i = m_width * m_height - 1; i >= 0; --i) {
const uint8 s = old[i];
Color3uint8& d = ((Color3uint8*)m_byte)[i];
d.r = d.g = d.b = s;
}
m_memMan->free(old);
}
break;
case 3:
return;
case 4:
// Strip alpha
{
Color4uint8* old = (Color4uint8*)m_byte;
m_byte = NULL;
resize(m_width, m_height, 3);
for (int i = m_width * m_height - 1; i >= 0; --i) {
const Color4uint8 s = old[i];
Color3uint8& d = ((Color3uint8*)m_byte)[i];
d.r = s.r;
d.g = s.g;
d.b = s.b;
}
m_memMan->free(old);
}
break;
default:
alwaysAssertM(false, "Bad number of channels in input image");
}
}
void GImage::R8G8B8_to_Y8U8V8(int width, int height, const uint8* _in, uint8* _out) {
const Color3uint8* in = reinterpret_cast<const Color3uint8*>(_in);
Color3uint8* out = reinterpret_cast<Color3uint8*>(_out);
Color3uint8 p;
for (int i = width * height - 1; i >= 0; --i) {
p.r = iClamp(iRound(in->r * 0.229 + in->g * 0.587 + in->b * 0.114), 0, 255);
p.g = iClamp(iRound(in->r * -0.147 + in->g * -0.289 + in->b * 0.436) + 127, 0, 255);
p.b = iClamp(iRound(in->r * 0.615 + in->g * -0.515 + in->b * -0.100) + 127, 0, 255);
*out = p;
++in;
++out;
}
}
void GImage::Y8U8V8_to_R8G8B8(int width, int height, const uint8* _in, uint8* _out) {
const Color3uint8* in = reinterpret_cast<const Color3uint8*>(_in);
Color3uint8* out = reinterpret_cast<Color3uint8*>(_out);
Color3uint8 p;
for (int i = width * height - 1; i >= 0; --i) {
p.r = iClamp(iRound(in->r * 1.0753 + (in->b - 127) * 1.2256), 0, 255);
p.g = iClamp(iRound(in->r * 1.0753 + (in->g - 127) * -0.3946 + (in->b - 127) * -0.4947), 0, 255);
p.b = iClamp(iRound(in->r * 1.0753 + (in->g - 127) * 2.0320 + (in->b - 127) * 0.0853), 0, 255);
*out = p;
++in;
++out;
}
}
void GImage::makeCheckerboard(GImage& im, int checkerSize, const Color4uint8& A, const Color4uint8& B) {
for (int y = 0; y < im.m_height; ++y) {
for (int x = 0; x < im.m_width; ++x) {
bool checker = isOdd((x / checkerSize) + (y / checkerSize));
const Color4uint8& color = checker ? A : B;
for (int c = 0; c < im.m_channels; ++c) {
uint8* v = im.byte() + (x + y * im.m_width) * im.m_channels + c;
*v = color[c];
}
}
}
}
}