#include "G3D/Array.h"
#include "G3D/g3dmath.h"
#include "G3D/stringutils.h"
#include "G3D/Color1uint8.h"
#include "G3D/Color3uint8.h"
#include "G3D/Color4uint8.h"
namespace G3D {
class BinaryInput;
class BinaryOutput;
/**
Interface to image compression & file formats.
Supported formats (decode and encode): Color JPEG, PNG, (Uncompressed)TGA 24, (Uncompressed)TGA 32, BMP 1, BMP 4, BMP 8, BMP 24, PPM (P6), and PPM ASCII (P1, P2, P3).
8-bit paletted PCX, 24-bit PCX, and ICO are supported for decoding only.
Sample usage:
#include "graphics3D.h"
// Loading from disk:
G3D::GImage im1 = G3D::GImage("test.jpg");
// Loading from memory:
G3D::GImage im2 = G3D::GImage(data, length);
// im.pixel is a pointer to RGB color data. If you want
// an alpha channel, call RGBtoRGBA or RGBtoARGB for
// conversion.
// Saving to memory:
G3D::GImage im3 = G3D::GImage(width, height);
// (Set the pixels of im3...)
uint8* data2;
int len2;
im3.encode(G3D::GImage::JPEG, data2, len2);
// Saving to disk
im3.save("out.jpg");
The free Image Magick Magick Wand API
(http://www.imagemagick.org/www/api/magick_wand.html) provides a more powerful
API for image manipulation and wider set of image load/save formats. It is
recommended over GImage (we don't include it directly in G3D because their license
is more restrictive than the BSD one).
*/
class GImage {
private:
uint8* _byte;
public:
class Error {
public:
Error(
const std::string& reason,
const std::string& filename = "") :
reason(reason), filename(filename) {}
std::string reason;
std::string filename;
};
enum Format {JPEG, BMP, TGA, PCX, ICO, PNG, PPM_ASCII, PPM, AUTODETECT, UNKNOWN};
int width;
int height;
/**
The number of channels; either 3 (RGB) or 4 (RGBA)
*/
int channels;
inline const uint8* byte() const {
return _byte;
}
/** Returns a pointer to the upper left pixel
as Color3uint8.
*/
inline const Color3uint8* pixel3() const {
debugAssertM(channels == 3, format("Tried to call GImage::pixel3 on an image with %d channels", channels));
return (Color3uint8*)_byte;
}
/** Returns a pointer to the upper left pixel
as Color4uint8.
*/
inline const Color4uint8* pixel4() const {
debugAssertM(channels == 4, format("Tried to call GImage::pixel4 on an image with %d channels", channels));
return (Color4uint8*)_byte;
}
inline const Color1uint8* pixel1() const {
debugAssertM(channels == 1, format("Tried to call GImage::pixel1 on an image with %d channels", channels));
return (Color1uint8*)_byte;
}
inline Color1uint8* pixel1() {
debugAssertM(channels == 1, format("Tried to call GImage::pixel1 on an image with %d channels", channels));
return (Color1uint8*)_byte;
}
/** Returns the pixel at (x, y), where (0,0) is the upper left. */
inline const Color1uint8& pixel1(int x, int y) const {
debugAssert(x >= 0 && x < width);
debugAssert(y >= 0 && y < height);
return pixel1()[x + y * width];
}
/** Returns the pixel at (x, y), where (0,0) is the upper left. */
inline Color1uint8& pixel1(int x, int y) {
debugAssert(x >= 0 && x < width);
debugAssert(y >= 0 && y < height);
return pixel1()[x + y * width];
}
/** Returns the pixel at (x, y), where (0,0) is the upper left. */
inline const Color3uint8& pixel3(int x, int y) const {
debugAssert(x >= 0 && x < width);
debugAssert(y >= 0 && y < height);
return pixel3()[x + y * width];
}
inline Color3uint8& pixel3(int x, int y) {
debugAssert(x >= 0 && x < width);
debugAssert(y >= 0 && y < height);
return pixel3()[x + y * width];
}
/** Returns the pixel at (x, y), where (0,0) is the upper left. */
inline const Color4uint8& pixel4(int x, int y) const {
debugAssert(x >= 0 && x < width);
debugAssert(y >= 0 && y < height);
return pixel4()[x + y * width];
}
inline Color4uint8& pixel4(int x, int y) {
debugAssert(x >= 0 && x < width);
debugAssert(y >= 0 && y < height);
return pixel4()[x + y * width];
}
inline uint8* byte() {
return _byte;
}
inline Color3uint8* pixel3() {
debugAssert(channels == 3);
return (Color3uint8*)_byte;
}
inline Color4uint8* pixel4() {
debugAssert(channels == 4);
return (Color4uint8*)_byte;
}
private:
void encodeBMP(
BinaryOutput& out) const;
/**
The TGA file will be either 24- or 32-bit depending
on the number of channels.
*/
void encodeTGA(
BinaryOutput& out) const;
/**
Converts this image into a JPEG
*/
void encodeJPEG(
BinaryOutput& out) const;
/**
Converts this image into a JPEG
*/
void encodePNG(
BinaryOutput& out) const;
void encodePPM(
BinaryOutput& out) const;
void encodePPMASCII(
BinaryOutput& out) const;
void decodeTGA(
BinaryInput& input);
void decodeBMP(
BinaryInput& input);
void decodeJPEG(
BinaryInput& input);
void decodePCX(
BinaryInput& input);
void decodeICO(
BinaryInput& input);
void decodePNG(
BinaryInput& input);
void decodePPM(
BinaryInput& input);
void decodePPMASCII(
BinaryInput& input);
/**
Given [maybe] a filename, memory buffer, and [maybe] a format,
returns the most likely format of this file.
*/
static Format resolveFormat(
const std::string& filename,
const uint8* data,
int dataLen,
Format maybeFormat);
void _copy(
const GImage& other);
public:
static Format resolveFormat(const std::string& filename);
GImage() {
width = height = channels = 0;
_byte = NULL;
}
/**
Load an encoded image from disk and decode it.
Throws GImage::Error if something goes wrong.
*/
GImage(
const std::string& filename,
Format format = AUTODETECT);
/**
Decodes an image stored in a buffer.
*/
GImage(
const unsigned char*data,
int length,
Format format = AUTODETECT);
/**
Create an empty image of the given size.
*/
GImage(
int width,
int height,
int channels = 3);
GImage(
const GImage& other);
GImage& operator=(const GImage& other);
/**
Returns a new GImage that has 4 channels. RGB is
taken from this GImage and the alpha from the red
channel of the second image.
*/
GImage insertRedAsAlpha(const GImage& alpha) const;
/**
Returns a new GImage with 3 channels, removing
the alpha channel if there is one.
*/
GImage stripAlpha() const;
/**
Loads an image from disk (clearing the old one first).
*/
void load(
const std::string& filename,
Format format = AUTODETECT);
/**
Frees memory and resets to a 0x0 image.
*/
void clear();
/**
Deallocates the pixels.
*/
virtual ~GImage();
/**
Resizes the internal buffer to (width x height) with the
number of channels specified. All data is set to 0 (black).
*/
void resize(int width, int height, int channels);
/**
Copies src sub-image data into dest at a certain offset.
The dest variable must already contain an image that is large
enough to contain the src sub-image at the specified offset.
Returns true on success and false if the src sub-image cannot
completely fit within dest at the specified offset. Both
src and dest must have the same number of channels.
*/
static bool pasteSubImage(GImage & dest, const GImage & src,
int destX, int destY, int srcX, int srcY, int srcWidth, int srcHeight);
/**
creates dest from src sub-image data.
Returns true on success and false if the src sub-image
is not within src.
*/
static bool copySubImage(GImage & dest, const GImage & src,
int srcX, int srcY, int srcWidth, int srcHeight);
void convertToRGBA();
void convertToRGB();
/** Averages color channels if they exist */
void convertToL8();
/**
Returns true if format is supported. Format
should be an extension string (e.g. "BMP").
*/
static bool supportedFormat(
const std::string& format);
/**
Converts a string to an enum, returns UNKNOWN if not recognized.
*/
static Format stringToFormat(
const std::string& format);
/**
Encode and save to disk.
*/
void save(
const std::string& filename,
Format format = AUTODETECT) const;
/**
The caller must delete the returned buffer.
*/
void encode(
Format format,
uint8*& outData,
int& outLength) const;
/**
Does not commit the BinaryOutput when done.
*/
void encode(
Format format,
BinaryOutput& out) const;
/**
Decodes the buffer into this image.
@format Must be the correct format.
*/
void decode(
BinaryInput& input,
Format format);
/** Returns the size of this object in bytes */
int sizeInMemory() const;
/** Ok for in == out */
static void R8G8B8_to_Y8U8V8(int width, int height, const uint8* in, uint8* out);
/** Ok for in == out */
static void Y8U8V8_to_R8G8B8(int width, int height, const uint8* in, uint8* out);
/**
@param in RGB buffer of numPixels * 3 bytes
@param out Buffer of numPixels * 4 bytes
@param numPixels Number of RGB pixels to convert
*/
static void RGBtoRGBA(
const uint8* in,
uint8* out,
int numPixels);
static void RGBtoARGB(
const uint8* in,
uint8* out,
int numPixels);
/** Safe for in == out */
static void RGBtoBGR(
const uint8* in,
uint8* out,
int numPixels);
/**
Win32 32-bit HDC format.
*/
static void RGBtoBGRA(
const uint8* in,
uint8* out,
int numPixels);
static void RGBAtoRGB(
const uint8* in,
uint8* out,
int numPixels);
/**
Uses the red channel of the second image as an alpha channel.
*/
static void RGBxRGBtoRGBA(
const uint8* colorRGB,
const uint8* alphaRGB,
uint8* out,
int numPixels);
/**
Flips the image along the vertical axis.
Safe for in == out.
*/
static void flipRGBVertical(
const uint8* in,
uint8* out,
int width,
int height);
static void flipRGBAVertical(
const uint8* in,
uint8* out,
int width,
int height);
/**
Given a tangent space bump map, computes a new image where the
RGB channels are a tangent space normal map and the alpha channel
is the original bump map. Assumes the input image is tileable.
In the resulting image, x = red = tangent, y = green = binormal, and z = blue = normal.
Particularly useful as part of the idiom:
GImage normal;
computeNormalMap(GImage(filename), normal);
return Texture::fromGImage(filename, normal);
@param lowPassBump If true, a 9x9 filter of 1's is used to low-pass filter the elevations,
which produces better results for parallax mapping.
@param scaleHeightByNz After computing normals, scale the height by |N.z|, a trick that
reduces texture swim in steep areas for parallax mapping.
@param whiteHeightInPixels How high should the brightest input value be considered for purposes
of normal computation, compared to the horizontal and vertical size of a pixel.
A value of 255 means that a 255 x 255 bump image with a full black-to-white gradient will
produce a 45-degree ramp (this also results in "cubic" voxels).
A special (default) value of -1 means scale the effective white height so that it is equal
to the larger spatial dimension.
*/
static void computeNormalMap(
const class GImage& bump,
class GImage& normal,
float whiteHeightInPixels = -1.0f,
bool lowPassBump = false,
bool scaleHeightByNz = false);
static void computeNormalMap(
int width,
int height,
int channels,
const uint8* src,
GImage& normal,
float whiteHeightInPixels,
bool lowPassBump,
bool scaleHeightByNz);
/**
Bayer demosaicing using the filter proposed in
HIGH-QUALITY LINEAR INTERPOLATION FOR DEMOSAICING OF BAYER-PATTERNED COLOR IMAGES
Henrique S. Malvar, Li-wei He, and Ross Cutler
The filter wraps at the image boundaries.
Assumes in != out.
*/
static void BAYER_G8B8_R8G8_to_R8G8B8_MHC(int w, int h, const uint8* in, uint8* _out);
static void BAYER_G8R8_B8G8_to_R8G8B8_MHC(int w, int h, const uint8* in, uint8* _out);
static void BAYER_R8G8_G8B8_to_R8G8B8_MHC(int w, int h, const uint8* in, uint8* _out);
static void BAYER_B8G8_G8R8_to_R8G8B8_MHC(int w, int h, const uint8* in, uint8* _out);
/** Fast conversion; the output has 1/2 the size of the input in each direction. Assumes in != out.
See G3D::BAYER_G8B8_R8G8_to_R8G8B8_MHC for a much better result. */
static void BAYER_G8B8_R8G8_to_Quarter_R8G8B8(int inWidth, int inHeight, const uint8* in, uint8* out);
/** Attempt to undo fast conversion of G3D::BAYER_G8B8_R8G8_to_Quarter_R8G8B8;
the green channel will lose data. Assumes in != out
The input should have size 3 * inWidth * inHeight. The output should have size
2 * inWidth * 2 * inHeight.
*/
static void Quarter_R8G8B8_to_BAYER_G8B8_R8G8(int inWidth, int inHeight, const uint8* in, uint8* out);
/** Overwrites every pixel with one of the two colors in a checkerboard pattern.
The fields used from the two colors depend on the current number of channels in @a im.
*/
static void makeCheckerboard(GImage& im, int checkerSize = 1, const Color4uint8& color1 = Color4uint8(255,255,255,255), const Color4uint8& color2 = Color4uint8(0,0,0,255));
};
}
#endif