/** \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 #else # include "png.h" #endif #include #include #include ////////////////////////////////////////////////////////////////////////////////////////////// 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(_in); Color3uint8* out = reinterpret_cast(_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(_in); Color3uint8* out = reinterpret_cast(_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]; } } } } }