From e0b6f69b9d24dbe203db573478c43ed4fc11c90b Mon Sep 17 00:00:00 2001 From: danij Date: Wed, 2 Mar 2011 09:50:07 +0000 Subject: [PATCH] Further preparatory refactoring and cleanup of the texture loading/processing pipeline. --- doomsday/build/codeblocks/doomsday.cbp | 1 + doomsday/build/win32/doomsday_cl.rsp | 1 + doomsday/build/win32/vs8/doomsday.vcproj | 4 + doomsday/engine/portable/include/gl_main.h | 115 +- doomsday/engine/portable/include/gl_tex.h | 192 ++- .../engine/portable/include/gl_texmanager.h | 12 +- doomsday/engine/portable/include/image.h | 14 + doomsday/engine/portable/include/m_misc.h | 12 + doomsday/engine/portable/include/r_data.h | 4 - doomsday/engine/portable/src/dgl_texture.c | 311 ++-- doomsday/engine/portable/src/gl_main.c | 299 ++++ doomsday/engine/portable/src/gl_tex.c | 1302 ++++++++--------- doomsday/engine/portable/src/gl_texmanager.c | 382 ++--- doomsday/engine/portable/src/image.c | 112 ++ doomsday/engine/portable/src/m_misc.c | 37 + doomsday/engine/portable/src/r_lumobjs.c | 2 +- doomsday/engine/portable/src/rend_particle.c | 2 +- 17 files changed, 1520 insertions(+), 1282 deletions(-) create mode 100644 doomsday/engine/portable/src/image.c diff --git a/doomsday/build/codeblocks/doomsday.cbp b/doomsday/build/codeblocks/doomsday.cbp index 0b935b6490..71b712687f 100644 --- a/doomsday/build/codeblocks/doomsday.cbp +++ b/doomsday/build/codeblocks/doomsday.cbp @@ -542,6 +542,7 @@ + diff --git a/doomsday/build/win32/doomsday_cl.rsp b/doomsday/build/win32/doomsday_cl.rsp index 13a404f026..bc19dd5dd7 100644 --- a/doomsday/build/win32/doomsday_cl.rsp +++ b/doomsday/build/win32/doomsday_cl.rsp @@ -44,6 +44,7 @@ .\..\..\engine\portable\src\gl_font.c .\..\..\engine\portable\src\gl_draw.c .\..\..\engine\portable\src\gl_drawvectorgraphic.c + .\..\..\engine\portable\src\image.c .\..\..\engine\portable\src\pathdirectory.c .\..\..\engine\portable\src\rend_console.c .\..\..\engine\portable\src\rend_bias.c diff --git a/doomsday/build/win32/vs8/doomsday.vcproj b/doomsday/build/win32/vs8/doomsday.vcproj index 6f262741a5..3b0e049da3 100644 --- a/doomsday/build/win32/vs8/doomsday.vcproj +++ b/doomsday/build/win32/vs8/doomsday.vcproj @@ -1140,6 +1140,10 @@ RelativePath="..\..\..\engine\portable\src\gl_tga.c" > + + diff --git a/doomsday/engine/portable/include/gl_main.h b/doomsday/engine/portable/include/gl_main.h index 32aa306c2e..018a808da9 100644 --- a/doomsday/engine/portable/include/gl_main.h +++ b/doomsday/engine/portable/include/gl_main.h @@ -69,15 +69,12 @@ void GL_SwitchTo3DState(boolean push_state, viewport_t* port); void GL_Restore2DState(int step, viewport_t* port); void GL_ProjectionMatrix(void); void GL_InfinitePerspective(DGLdouble fovy, DGLdouble aspect, DGLdouble znear); -void GL_RuntimeMode(void); void GL_DoUpdate(void); void GL_BlendMode(blendmode_t mode); void GL_InitRefresh(void); void GL_ShutdownRefresh(void); void GL_UseFog(int yes); -//void GL_InitVarFont(void); -//void GL_ShutdownVarFont(void); const char* GL_ChooseFixedFont(void); const char* GL_ChooseVariableFont(glfontstyle_t style, int resX, int resY); void GL_LowRes(void); @@ -105,17 +102,56 @@ void GL_DrawElements(dglprimtype_t type, int count, const uint* indices); boolean GL_Grab(int x, int y, int width, int height, dgltexformat_t format, void* buffer); -boolean GL_TexImage(dgltexformat_t format, DGLuint palid, int width, - int height, int genMips, void* data); -int GL_GetTexAnisoMul(int level); -DGLuint GL_CreateColorPalette(const int compOrder[3], - const byte compSize[3], - const byte* data, ushort num); -void GL_DeleteColorPalettes(DGLsizei n, const DGLuint* palettes); +/** + * @param format DGL texture format symbolic, one of: + * DGL_RGB + * DGL_RGBA + * DGL_COLOR_INDEX_8 + * DGL_COLOR_INDEX_8_PLUS_A8 + * DGL_LUMINANCE + * @param palid Id of the color palette to use with this texture. Only has + * meaning if the input format is one of: + * DGL_COLOR_INDEX_8 + * DGL_COLOR_INDEX_8_PLUS_A8 + * @param width Width of the texture, must be power of two. + * @param height Height of the texture, must be power of two. + * @param genMips If negative sets a specific mipmap level, e.g.: + * @c -1, means mipmap level 1. + * @param data Ptr to the texture data. + * + * @return @c true iff successful. + */ +boolean GL_TexImage(dgltexformat_t format, DGLuint palid, int width, int height, + int genMips, void* data); + +/** + * Given a logical anisotropic filtering level return an appropriate multiplier + * according to the current GL state and user configuration. + */ +int GL_GetTexAnisoMul(int level); + +/** + * How many mipmap levels are needed for a texture of the given dimensions? + * + * @param width Logical width of the texture in pixels. + * @param height Logical height of the texture in pixels. + * @return Number of mipmap levels required. + */ +int GL_NumMipmapLevels(int width, int height); + +/** + * @param compOrder Component order. Examples: + * [0,1,2] == RGB + * [2,1,0] == BGR + * @param compSize Number of bits per component [R,G,B]. + */ +DGLuint GL_CreateColorPalette(const int compOrder[3], const uint8_t compSize[3], + const uint8_t* data, ushort num); + +void GL_DeleteColorPalettes(DGLsizei n, const DGLuint* palettes); -void GL_GetColorPaletteRGB(DGLuint id, DGLubyte rgb[3], - ushort idx); +void GL_GetColorPaletteRGB(DGLuint id, DGLubyte rgb[3], ushort idx); boolean GL_PalettizeImage(uint8_t* out, int outformat, DGLuint palid, boolean gammaCorrect, const uint8_t* in, int informat, int width, int height); @@ -123,13 +159,64 @@ boolean GL_PalettizeImage(uint8_t* out, int outformat, DGLuint palid, boolean GL_QuantizeImageToPalette(uint8_t* out, int outformat, DGLuint palid, const uint8_t* in, int informat, int width, int height); -void GL_DeSaturatePalettedImage(byte* buffer, DGLuint palid, - int width, int height); +/** + * Desaturates the texture in the dest buffer by averaging the colour then + * looking up the nearest match in the palette. Increases the brightness + * to maximum. + */ +void GL_DeSaturatePalettedImage(byte* buffer, DGLuint palid, int width, int height); // Returns a pointer to a copy of the screen. The pointer must be // deallocated by the caller. unsigned char* GL_GrabScreen(void); +/** + * in/out format: + * 1 = palette indices + * 2 = palette indices followed by alpha values + * 3 = RGB + * 4 = RGBA + */ +uint8_t* GL_ConvertBuffer(const uint8_t* src, int width, int height, + int comps, colorpaletteid_t pal, boolean gamma, int outformat); + +/** + * @param method Unique identifier of the smart filtering method to apply. + * @param src Source image to be filtered. + * @param width Logical width of the source image in pixels. + * @param height Logical height of the source image in pixels. + * @param flags @see imageConversionFlags. + * @param outWidth Logical width of resultant image in pixels. + * @param outHeight Logical height of resultant image in pixels. + * + * @return Newly allocated version of the source image if filtered else @c == @a src. + */ +uint8_t* GL_SmartFilter(int method, const uint8_t* src, int width, int height, + int flags, int* outWidth, int* outHeight); + +/** + * Calculates the properties of a dynamic light that the given sprite frame + * casts. Crop a boundary around the image to remove excess alpha'd pixels + * from adversely affecting the calculation. + * Handles pixel sizes; 1 (==2), 3 and 4. + */ +void GL_CalcLuminance(const uint8_t* buffer, int width, int height, int comps, + colorpaletteid_t palid, float* brightX, float* brightY, float color[3], + float* lumSize); + +/** + * @param width Logical width of the image in pixels. + * @param height Logical height of the image in pixels. + * @param flags @see imageConversionFlags. + */ +int GL_ChooseSmartFilter(int width, int height, int flags); + +/** + * The given RGB color is scaled uniformly so that the highest component + * becomes one. + */ +void amplify(float rgb[3]); + // Console commands. D_CMD(UpdateGammaRamp); diff --git a/doomsday/engine/portable/include/gl_tex.h b/doomsday/engine/portable/include/gl_tex.h index cc10ce8680..9dd1f7742e 100644 --- a/doomsday/engine/portable/include/gl_tex.h +++ b/doomsday/engine/portable/include/gl_tex.h @@ -23,83 +23,163 @@ */ /** - * Texture Manipulation Algorithms. + * Image manipulation and evaluation algorithms. */ -#ifndef LIBDENG_TEXTURES_H -#define LIBDENG_TEXTURES_H - -struct image_s; - -boolean GL_OptimalSize(int width, int height, int* optWidth, int* optHeight, - boolean noStretch, boolean isMipMapped); +#ifndef LIBDENG_IMAGE_MANIPULATION_H +#define LIBDENG_IMAGE_MANIPULATION_H /** - * in/out format: - * 1 = palette indices - * 2 = palette indices followed by alpha values - * 3 = RGB - * 4 = RGBA + * @param pixels Luminance image to be enhanced. + * @param width Logical width of the image in pixels. + * @param height Logical height of the image in pixels. + * @param hasAlpha @c true == @a pixels is luminance plus alpha data. */ -void GL_ConvertBuffer(int width, int height, int informat, int outformat, - const uint8_t* src, uint8_t* dst, colorpaletteid_t pal, boolean gamma); +void AmplifyLuma(uint8_t* pixels, int width, int height, boolean hasAlpha); -uint8_t* GL_ScaleBuffer32(const uint8_t* src, int width, int height, int comps, - int outWidth, int outHeight); - -void GL_DownMipmap32(byte* in, int width, int height, int comps); -void GL_ConvertToAlpha(struct image_s *image, boolean makeWhite); -void GL_ConvertToLuminance(struct image_s *image); -void GL_CalcLuminance(byte* buffer, int width, int height, - int pixelsize, colorpaletteid_t palid, - float* brightX, float* brightY, - rgbcol_t* color, float* lumSize); -byte* GL_ApplyColorKeying(byte* buf, unsigned int pixelSize, - unsigned int width, unsigned int height); -int GL_ValidTexHeight2(int width, int height); +/** + * Take the input buffer and convert to color keyed. A new buffer may be + * needed if the input buffer has three color components. + * + * @return If the in buffer wasn't large enough will return a ptr to the + * newly allocated buffer which must be freed with free(), else @a buf. + */ +uint8_t* ApplyColorKeying(uint8_t* pixels, int width, int height, + int pixelSize); -void pixBlt(byte* src, int srcWidth, int srcHeight, byte* dest, - int destWidth, int destHeight, int alpha, int srcRegX, - int srcRegY, int destRegX, int destRegY, int regWidth, - int regHeight); -void averageColorIdx(rgbcol_t col, byte* data, int w, int h, - colorpaletteid_t palid, boolean hasAlpha); -void averageColorRGB(rgbcol_t col, byte* data, int w, int h); -int lineAverageColorIdx(rgbcol_t col, byte* data, int w, int h, - int line, colorpaletteid_t palid, - boolean hasAlpha); -int lineAverageColorRGB(rgbcol_t col, byte* data, int w, int h, - int line); -void amplify(float* rgb); +/** + * @param pixels RGBA data. Input read here, and output written here. + * @param width Logical width of the image in pixels. + * @param height Logical height of the image in pixels. + */ +#if 0 // dj: Doesn't make sense, "darkness" applied to an alpha channel? +void BlackOutlines(uint8_t* pixels, int width, int height); +#endif /** * Spread the color of none masked pixels outwards into the masked area. * This addresses the "black outlines" produced by texture filtering due to * sampling the default (black) color. */ -void ColorOutlines(uint8_t* buffer, int width, int height); +void ColorOutlinesIdx(uint8_t* pixels, int width, int height); -boolean ImageHasAlpha(struct image_s *image); +/** + * @param pixels RGB(a) image to be enhanced. + * @param width Logical width of the image in pixels. + * @param height Logical height of the image in pixels. + * @param pixelsize Size of each pixel. Handles 3 and 4. + */ +void Desaturate(uint8_t* pixels, int width, int height, int pixelSize); /** + * \important Does not conform to any standard technique and adjustments + * are applied symmetrically for all color components. + * + * @param pixels RGB(a) image to be enhanced. + * @param width Logical width of the image in pixels. + * @param height Logical height of the image in pixels. + * @param pixelsize Size of each pixel. Handles 3 and 4. + */ +void EnhanceContrast(uint8_t* pixels, int width, int height, int pixelSize); + +/** + * Equalize the specified luminance map such that the minimum and maximum + * brightness covers the whole [0...255] range. + * + * \algorithm Calculates shift deltas for bright and dark-side pixels by + * averaging the luminosity of all pixels in the original image. + * + * @param pixels Luminance image to equalize. * @param width Logical width of the image in pixels. * @param height Logical height of the image in pixels. - * @param flags @see imageConversionFlags. */ -int GL_ChooseSmartFilter(int width, int height, int flags); +void EqualizeLuma(uint8_t* pixels, int width, int height, float* rBaMul, + float* rHiMul, float* rLoMul); + +/** + * @param pixels RGB(a) image to evaluate. + * @param width Logical width of the image in pixels. + * @param height Logical height of the image in pixels. + * @param color Determined average color written here. + */ +void FindAverageColor(const uint8_t* pixels, int width, int height, + int pixelSize, float color[3]); + +/** + * @param pixels Index-color image to evaluate. + * @param width Logical width of the image in pixels. + * @param height Logical height of the image in pixels. + * @param palid Unique identifier of the color palette to use. + * @param hasAlpha @c true == @a pixels includes alpha data. + * @param color Determined average color written here. + */ +void FindAverageColorIdx(const uint8_t* pixels, int width, int height, + colorpaletteid_t palid, boolean hasAlpha, float color[3]); + +/** + * @param pixels RGB(a) image to evaluate. + * @param width Logical width of the image in pixels. + * @param height Logical height of the image in pixels. + * @param color Determined average color written here. + */ +void FindAverageLineColor(const uint8_t* pixels, int width, int height, + int pixelSize, int line, float color[3]); + +/** + * @param pixels Index-color image to evaluate. + * @param width Logical width of the image in pixels. + * @param height Logical height of the image in pixels. + * @param palid Unique identifier of the color palette to use. + * @param hasAlpha @c true == @a pixels includes alpha data. + * @param color Determined average color written here. + */ +void FindAverageLineColorIdx(const uint8_t* pixels, int width, int height, + int line, colorpaletteid_t palid, boolean hasAlpha, float color[3]); + +/** + * Calculates a clip region for the image that excludes alpha pixels. + * \algorithm: Cross spread from bottom > top, right > left (inside out). + * + * @param pixels Image data to be processed. + * @param width Logical width of the image in pixels. + * @param height Logical height of the image in pixels. + * @param pixelsize Size of each pixel. Handles 1 (==2), 3 and 4. + * @param region Determined region written here. + */ +void FindClipRegionNonAlpha(const uint8_t* pixels, int width, int height, + int pixelSize, int region[4]); + +/** + * @param pixels RGB(a) image to be enhanced. + * @param width Logical width of the image in pixels. + * @param height Logical height of the image in pixels. + * @param pixelsize Size of each pixel. Handles 3 and 4. + */ +void SharpenPixels(uint8_t* pixels, int width, int height, int pixelSize); + +uint8_t* GL_ScaleBuffer(const uint8_t* pixels, int width, int height, + int pixelSize, int outWidth, int outHeight); + +uint8_t* GL_ScaleBufferNearest(const uint8_t* pixels, int width, int height, + int pixelSize, int outWidth, int outHeight); + +/** + * Works within the given data, reducing the size of the picture to half + * its original. + * + * @param width Width of the final texture, must be power of two. + * @param height Height of the final texture, must be power of two. + */ +void GL_DownMipmap32(uint8_t* pixels, int width, int height, int pixelSize); /** - * @param method Unique identifier of the smart filtering method to apply. - * @param src Source image to be filtered. - * @param width Logical width of the source image in pixels. - * @param height Logical height of the source image in pixels. - * @param flags @see imageConversionFlags. - * @param outWidth Logical width of resultant image in pixels. - * @param outHeight Logical height of resultant image in pixels. + * Works within the given data, reducing the size of the picture to half + * its original. * - * @return Newly allocated version of the source image if filtered else @c == @a src. + * @param width Width of the final texture, must be power of two. + * @param height Height of the final texture, must be power of two. */ -uint8_t* GL_SmartFilter(int method, const uint8_t* src, int width, int height, - int flags, int* outWidth, int* outHeight); +void GL_DownMipmap8(uint8_t* in, uint8_t* fadedOut, int width, int height, + float fade); -#endif /* LIBDENG_TEXTURES_H */ +#endif /* LIBDENG_IMAGE_MANIPULATION_H */ diff --git a/doomsday/engine/portable/include/gl_texmanager.h b/doomsday/engine/portable/include/gl_texmanager.h index 1ac4137b4c..fa333f02ff 100644 --- a/doomsday/engine/portable/include/gl_texmanager.h +++ b/doomsday/engine/portable/include/gl_texmanager.h @@ -94,7 +94,7 @@ typedef struct gltexture_inst_s { struct { boolean pSprite; // @c true, iff this is for use as a psprite. float flareX, flareY, lumSize; - rgbcol_t autoLightColor; + float autoLightColor[3]; float texCoord[2]; // Prepared texture coordinates. int tmap, tclass; // Color translation. } sprite; @@ -205,6 +205,16 @@ void GL_SetTranslatedSprite(material_t* mat, int tclass, int tmap); void GL_SetRawImage(lumpnum_t lump, int wrapS, int wrapT); +/** + * Determine the optimal size for a texture. Usually the dimensions are + * scaled upwards to the next power of two. + * + * @param noStretch If @c true, the stretching can be skipped. + * @param isMipMapped If @c true, we will require mipmaps (this has an + * effect on the optimal size). + */ +boolean GL_OptimalSize(int width, int height, boolean noStretch, + boolean isMipMapped, int* optWidth, int* optHeight); // Management of and access to gltextures (via the texmanager): const gltexture_t* GL_CreateGLTexture(const char* name, int ofTypeId, gltexture_type_t type); diff --git a/doomsday/engine/portable/include/image.h b/doomsday/engine/portable/include/image.h index 55074a1a4e..e9cee235ce 100644 --- a/doomsday/engine/portable/include/image.h +++ b/doomsday/engine/portable/include/image.h @@ -54,4 +54,18 @@ typedef struct image_s { #define ICF_UPSCALE_SAMPLE_WRAP (ICF_UPSCALE_SAMPLE_WRAPH|ICF_UPSCALE_SAMPLE_WRAPV) /*@}*/ +boolean GL_ImageHasAlpha(const image_t* image); + +/** + * Converts the image by converting it to a luminance map and then moving + * the resultant luminance data into the alpha channel. The color channel(s) + * are then filled all-white. + */ +void GL_ConvertToAlpha(image_t* image, boolean makeWhite); + +/** + * Converts the image data to grayscale luminance in-place. + */ +void GL_ConvertToLuminance(image_t* image); + #endif /* LIBDENG_IMAGE_H */ diff --git a/doomsday/engine/portable/include/m_misc.h b/doomsday/engine/portable/include/m_misc.h index 795259bf68..1908486556 100644 --- a/doomsday/engine/portable/include/m_misc.h +++ b/doomsday/engine/portable/include/m_misc.h @@ -73,6 +73,18 @@ const char* M_PrettyPath(const char* path); */ void M_ResolvePath(char* path); +/** + * Reads x bits from the source stream and writes them to out. + * + * \warning Output buffer must be large enough to hold at least @a numBits! + * + * @param numBits Number of bits to be read. + * @param src Current position in the source stream. + * @param cb Current byte. Used for tracking the current byte being read. + * @param out Read bits are ouput here. + */ +void M_ReadBits(uint numBits, const uint8_t** src, uint8_t* cb, uint8_t* out); + // Bounding boxes. void M_ClearBox(fixed_t* box); void M_CopyBox(fixed_t dest[4], const fixed_t src[4]); diff --git a/doomsday/engine/portable/include/r_data.h b/doomsday/engine/portable/include/r_data.h index d143b76bde..0f4f3ecf3f 100644 --- a/doomsday/engine/portable/include/r_data.h +++ b/doomsday/engine/portable/include/r_data.h @@ -114,10 +114,6 @@ typedef struct shadowlink_s { byte side; } shadowlink_t; -typedef float colorcomp_t; -typedef colorcomp_t rgbcol_t[3]; -typedef colorcomp_t rgbacol_t[4]; - typedef struct { lumpnum_t lump; short offX; // block origin (allways UL), which has allready diff --git a/doomsday/engine/portable/src/dgl_texture.c b/doomsday/engine/portable/src/dgl_texture.c index 999eddfe70..6cbe4a3000 100644 --- a/doomsday/engine/portable/src/dgl_texture.c +++ b/doomsday/engine/portable/src/dgl_texture.c @@ -22,15 +22,6 @@ * Boston, MA 02110-1301 USA */ -/** - * Textures and color palette handling. - * - * Get OpenGL header files from: - * http://oss.sgi.com/projects/ogl-sample/ - */ - -// HEADER FILES ------------------------------------------------------------ - #include #include @@ -41,44 +32,30 @@ #include "sys_opengl.h" -// MACROS ------------------------------------------------------------------ - #define RGB18(r, g, b) ((r)+((g)<<6)+((b)<<12)) -// TYPES ------------------------------------------------------------------- - // Color Palette Flags (CPF): #define CPF_UPDATE_18TO8 0x1 // The 18To8 table needs updating. typedef struct { - ushort num; - byte flags; // CPF_* flags. - DGLubyte* data; // R8G8B8 color triplets, [num * 3]. - ushort* pal18To8; // 262144 unique mappings. + ushort num; + uint8_t flags; /// CPF_* flags. + DGLubyte* data; /// RGB888 color triplets, [num * 3]. + ushort* pal18To8; /// 262144 unique mappings. } gl_colorpalette_t; -// FUNCTION PROTOTYPES ----------------------------------------------------- - -// EXTERNAL DATA DECLARATIONS ---------------------------------------------- - -// PUBLIC DATA DEFINITIONS ------------------------------------------------- - gl_state_texture_t GL_state_texture; -// PRIVATE DATA DEFINITIONS ------------------------------------------------ - static gl_colorpalette_t* colorPalettes = NULL; static DGLuint numColorPalettes = 0; -// CODE -------------------------------------------------------------------- - static void loadPalette(const gl_colorpalette_t* pal) { if(GL_state_texture.usePalTex) { - int i; - byte paldata[256 * 3]; - byte* buf; + uint8_t paldata[256 * 3]; + uint8_t* buf; + int i; if(pal->num > 256) buf = malloc(pal->num * 3); @@ -152,10 +129,10 @@ static void prepareColorPalette18To8(gl_colorpalette_t* pal) { if((pal->flags & CPF_UPDATE_18TO8) || !pal->pal18To8) { -#define SIZEOF18TO8 (sizeof(ushort) * 262144) // \fixme Too big? +#define SIZEOF18TO8 (sizeof(ushort) * 262144) - int r, g, b; - ushort closestIndex = 0; + ushort closestIndex = 0; + int r, g, b; if(!pal->pal18To8) { @@ -168,13 +145,13 @@ static void prepareColorPalette18To8(gl_colorpalette_t* pal) { for(b = 0; b < 64; ++b) { - ushort i; - int smallestDiff = DDMAXINT; + ushort i; + int smallestDiff = DDMAXINT; for(i = 0; i < pal->num; ++i) { - int diff; - const DGLubyte* rgb = &pal->data[i * 3]; + const DGLubyte* rgb = &pal->data[i * 3]; + int diff; diff = (rgb[CR] - (r << 2)) * (rgb[CR] - (r << 2)) + @@ -197,11 +174,10 @@ static void prepareColorPalette18To8(gl_colorpalette_t* pal) /*if(ArgCheck("-dump_pal18to8")) { - filename_t name; - FILE* file; + filename_t name; + FILE* file; - dd_snprintf(name, FILENAME_T_MAXLEN, "%s_18To8.lmp", - pal->name); + dd_snprintf(name, FILENAME_T_MAXLEN, "%s_18To8.lmp", pal->name); file = fopen(name, "wb"); fwrite(pal->pal18To8, SIZEOF18TO8, 1, file); @@ -212,52 +188,13 @@ static void prepareColorPalette18To8(gl_colorpalette_t* pal) } } -static void readBits(byte* out, const byte** src, byte* cb, uint numBits) +DGLuint GL_CreateColorPalette(const int compOrder[3], const uint8_t compSize[3], + const uint8_t* data, ushort num) { - int offset = 0, unread = numBits; - - // Read full bytes. - if(unread >= 8) + assert(compOrder && compSize && data); { - do - { - out[offset++] = **src, (*src)++; - } while((unread -= 8) >= 8); - } - - if(unread != 0) - { // Read remaining bits. - byte fb = 8 - unread; - - if((*cb) == 0) - (*cb) = 8; - - do - { - (*cb)--; - out[offset] <<= 1; - out[offset] |= ((**src >> (*cb)) & 0x01); - } while(--unread > 0); - - out[offset] <<= fb; - - if((*cb) == 0) - (*src)++; - } -} - -/** - * @param compOrder Component order. Examples: - * [0,1,2] == RGB - * [2,1,0] == BGR - * @param compSize Number of bits per component [R,G,B]. - */ -DGLuint GL_CreateColorPalette(const int compOrder[3], - const byte compSize[3], const byte* data, - ushort num) -{ - gl_colorpalette_t* pal; - byte order[3], bits[3]; + gl_colorpalette_t* pal; + uint8_t order[3], bits[3]; // Ensure input is in range. order[0] = MINMAX_OF(0, compOrder[0], 2); @@ -289,12 +226,11 @@ DGLuint GL_CreateColorPalette(const int compOrder[3], // Do we need to adjust the order? if(order[CR] != 0 || order[CG] != 1 || order[CB] != 2) { - uint i; - + uint i; for(i = 0; i < pal->num; ++i) { - byte* dst = &pal->data[i * 3]; - byte tmp[3]; + uint8_t* dst = &pal->data[i * 3]; + uint8_t tmp[3]; tmp[0] = dst[0]; tmp[1] = dst[1]; @@ -308,20 +244,20 @@ DGLuint GL_CreateColorPalette(const int compOrder[3], } else { // Another format entirely. - uint i; - byte cb = 0; - const byte* src = data; + const uint8_t* src = data; + uint8_t cb = 0; + uint i; for(i = 0; i < pal->num; ++i) { - byte* dst = &pal->data[i * 3]; - int tmp[3]; + uint8_t* dst = &pal->data[i * 3]; + int tmp[3]; tmp[CR] = tmp[CG] = tmp[CB] = 0; - readBits((byte*) &(tmp[order[CR]]), &src, &cb, bits[order[CR]]); - readBits((byte*) &(tmp[order[CG]]), &src, &cb, bits[order[CG]]); - readBits((byte*) &(tmp[order[CB]]), &src, &cb, bits[order[CB]]); + M_ReadBits(bits[order[CR]], &src, &cb, (uint8_t*) &(tmp[order[CR]])); + M_ReadBits(bits[order[CG]], &src, &cb, (uint8_t*) &(tmp[order[CG]])); + M_ReadBits(bits[order[CB]], &src, &cb, (uint8_t*) &(tmp[order[CB]])); // Need to do any scaling? if(bits[CR] != 8) @@ -349,9 +285,9 @@ DGLuint GL_CreateColorPalette(const int compOrder[3], } // Store the final color. - dst[CR] = MINMAX_OF(0, tmp[CR], 255); - dst[CG] = MINMAX_OF(0, tmp[CG], 255); - dst[CB] = MINMAX_OF(0, tmp[CB], 255); + dst[CR] = (uint8_t) MINMAX_OF(0, tmp[CR], 255); + dst[CG] = (uint8_t) MINMAX_OF(0, tmp[CG], 255); + dst[CB] = (uint8_t) MINMAX_OF(0, tmp[CB], 255); } } @@ -361,11 +297,12 @@ DGLuint GL_CreateColorPalette(const int compOrder[3], pal->pal18To8 = NULL; return numColorPalettes; // 1-based index. + } } void GL_DeleteColorPalettes(DGLsizei n, const DGLuint* palettes) { - DGLsizei i; + DGLsizei i; if(!(n > 0) || !palettes) return; @@ -374,15 +311,14 @@ void GL_DeleteColorPalettes(DGLsizei n, const DGLuint* palettes) { if(palettes[i] != 0 && palettes[i] - 1 < numColorPalettes) { - uint idx = palettes[i]-1; - gl_colorpalette_t* pal = &colorPalettes[idx]; + uint idx = palettes[i]-1; + gl_colorpalette_t* pal = &colorPalettes[idx]; Z_Free(pal->data); if(pal->pal18To8) Z_Free(pal->pal18To8); - memmove(&colorPalettes[idx], &colorPalettes[idx+1], - sizeof(*pal)); + memmove(&colorPalettes[idx], &colorPalettes[idx+1], sizeof(*pal)); numColorPalettes--; } } @@ -495,12 +431,7 @@ boolean GL_QuantizeImageToPalette(uint8_t* out, int outformat, DGLuint palid, return false; } -/** - * Desaturates the texture in the dest buffer by averaging the colour then - * looking up the nearest match in the palette. Increases the brightness - * to maximum. - */ -void GL_DeSaturatePalettedImage(byte* buffer, DGLuint palid, int width, int height) +void GL_DeSaturatePalettedImage(uint8_t* buffer, DGLuint palid, int width, int height) { const int numpels = width * height; const gl_colorpalette_t* pal; @@ -556,13 +487,12 @@ void GL_DeSaturatePalettedImage(byte* buffer, DGLuint palid, int width, int heig * Choose an internal texture format based on the number of color * components. * - * @param comps Number of color components. - * @return The internal texture format. + * @param comps Number of color components. + * @return The internal texture format. */ -GLenum ChooseFormat(int comps) +static GLenum ChooseTextureFormat(int comps) { - boolean compress = - (GL_state_texture.useCompr && GL_state.allowCompression); + boolean compress = (GL_state_texture.useCompr && GL_state.allowCompression); switch(comps) { @@ -578,7 +508,7 @@ GLenum ChooseFormat(int comps) : GL_COMPRESSED_RGBA; default: - Con_Error("drOpenGL.ChooseFormat: Unsupported comps: %i.", comps); + Con_Error("ChooseTextureFormat: Unsupported comps: %i.", comps); } // The fallback. @@ -587,7 +517,7 @@ GLenum ChooseFormat(int comps) int GL_GetTexAnisoMul(int level) { - int mul = 1; + int mul = 1; // Should anisotropic filtering be used? if(GL_state.useAnisotropic) @@ -621,95 +551,43 @@ int GL_GetTexAnisoMul(int level) return mul; } -/** - * Works within the given data, reducing the size of the picture to half - * its original. - * - * @param width Width of the final texture, must be power of two. - * @param height Height of the final texture, must be power of two. - */ -void downMip8(byte *in, byte *fadedOut, int width, int height, float fade) +static boolean GrayMipmap(dgltexformat_t format, uint8_t* data, int width, int height) { - byte *out = in; - int x, y, outW = width / 2, outH = height / 2; - float invFade; - - if(fade > 1) - fade = 1; - invFade = 1 - fade; - - if(width == 1 && height == 1) - { // Nothing can be done. - return; - } - - if(!outW || !outH) - { // Limited, 1x2|2x1 -> 1x1 reduction? - int outDim = (width > 1 ? outW : outH); - - for(x = 0; x < outDim; x++, in += 2) - { - *out = (in[0] + in[1]) / 2; - *fadedOut++ = (byte) (*out * invFade + 0x80 * fade); - out++; - } - } - else - { // Unconstrained, 2x2 -> 1x1 reduction? - for(y = 0; y < outH; y++, in += width) - for(x = 0; x < outW; x++, in += 2) - { - *out = (in[0] + in[1] + in[width] + in[width + 1]) / 4; - *fadedOut++ = (byte) (*out * invFade + 0x80 * fade); - out++; - } - } -} - -boolean grayMipmap(dgltexformat_t format, int width, int height, void *data) -{ - byte *image, *in, *out, *faded; - int i, numLevels, w, h, size = width * height, res; - uint comps = (format == DGL_LUMINANCE? 1 : 3); - float invFactor = 1 - GL_state_texture.grayMipmapFactor; + assert(data); + { + int numLevels = GL_NumMipmapLevels(width, height); + uint8_t* image, *in, *out, *faded; + int i, w, h, size = width * height, res; + uint comps = (format == DGL_LUMINANCE? 1 : 3); + float invFactor = 1 - GL_state_texture.grayMipmapFactor; + GLenum glTexFormat = ChooseTextureFormat(1); // Buffer used for the faded texture. - faded = M_Malloc(size / 4); - image = M_Malloc(size); + faded = malloc(size / 4); + image = malloc(size); // Initial fading. if(format == DGL_LUMINANCE || format == DGL_RGB) { for(i = 0, in = data, out = image; i < size; ++i, in += comps) { - res = (int) (*in * GL_state_texture.grayMipmapFactor + 0x80 * invFactor); - - // Clamp to [0, 255]. - if(res < 0) - res = 0; - if(res > 255) - res = 255; - - *out++ = res; + res = (int) (*in * GL_state_texture.grayMipmapFactor + 127 * invFactor); + *out++ = MINMAX_OF(0, res, 255); } } - // How many levels will there be? - for(numLevels = 0, w = width, h = height; w > 1 || h > 1; - w /= 2, h /= 2, numLevels++); - // We do not want automatical mipmaps. if(GL_state_ext.genMip) glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_FALSE); // Upload the first level right away. - glTexImage2D(GL_TEXTURE_2D, 0, ChooseFormat(1), width, height, 0, + glTexImage2D(GL_TEXTURE_2D, 0, glTexFormat, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image); // Generate all mipmaps levels. for(i = 0, w = width, h = height; i < numLevels; ++i) { - downMip8(image, faded, w, h, (i * 1.75f) / numLevels); + GL_DownMipmap8(image, faded, w, h, (i * 1.75f) / numLevels); // Go down one level. if(w > 1) @@ -717,54 +595,36 @@ boolean grayMipmap(dgltexformat_t format, int width, int height, void *data) if(h > 1) h /= 2; - glTexImage2D(GL_TEXTURE_2D, i + 1, ChooseFormat(1), w, h, 0, + glTexImage2D(GL_TEXTURE_2D, i + 1, glTexFormat, w, h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, faded); } // Do we need to free the temp buffer? - M_Free(faded); - M_Free(image); + free(faded); + free(image); if(GL_state.useAnisotropic) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, GL_GetTexAnisoMul(-1 /*best*/)); + return true; + } } -/** - * @param format DGL texture format symbolic, one of: - * DGL_RGB - * DGL_RGBA - * DGL_COLOR_INDEX_8 - * DGL_COLOR_INDEX_8_PLUS_A8 - * DGL_LUMINANCE - * @param palid Id of the color palette to use with this texture. - * Only has meaning if the input format is one of: - * DGL_COLOR_INDEX_8 - * DGL_COLOR_INDEX_8_PLUS_A8 - * @param width Width of the texture, must be power of two. - * @param height Height of the texture, must be power of two. - * @param genMips If negative, sets a specific mipmap level, - * e.g. @c -1, means mipmap level 1. - * @param data Ptr to the texture data. - * - * @return @c true iff successful. - */ -boolean GL_TexImage(dgltexformat_t format, DGLuint palid, - int width, int height, int genMips, void *data) +boolean GL_TexImage(dgltexformat_t format, DGLuint palid, int width, + int height, int genMips, void* data) { - int mipLevel = 0; - byte* bdata = data; + uint8_t* bdata = data; + int mipLevel = 0; - // Negative genMips values mean that the specific mipmap level is - // being uploaded. + // Negative genMips values mean that the specific mipmap level is being uploaded. if(genMips < 0) { mipLevel = -genMips; genMips = 0; } - // Can't operate on the null texture. + // Can't operate on null texture. if(!data) return false; @@ -785,7 +645,7 @@ boolean GL_TexImage(dgltexformat_t format, DGLuint palid, // Special fade-to-gray luminance texture? (used for details) if(genMips == DDMAXINT) { - return grayMipmap(format, width, height, data); + return GrayMipmap(format, data, width, height); } // Automatic mipmap generation? @@ -817,10 +677,10 @@ boolean GL_TexImage(dgltexformat_t format, DGLuint palid, (format == DGL_COLOR_INDEX_8_PLUS_A8) || (format == DGL_LUMINANCE_PLUS_A8)); int loadFormat = GL_RGBA; - int glFormat = ChooseFormat(alphachannel ? 4 : 3); + int glFormat = ChooseTextureFormat(alphachannel ? 4 : 3); int numPixels = width * height; - byte* buffer; - byte* pixel, *in; + uint8_t* buffer; + uint8_t* pixel, *in; boolean needFree = false; // Convert to either RGB or RGBA, if necessary. @@ -841,15 +701,14 @@ boolean GL_TexImage(dgltexformat_t format, DGLuint palid, int i; needFree = true; - buffer = M_Malloc(numPixels * 4); + buffer = malloc(numPixels * 4); if(!buffer) return false; switch(format) { case DGL_RGB: - for(i = 0, pixel = buffer, in = bdata; i < numPixels; - i++, pixel += 4) + for(i = 0, pixel = buffer, in = bdata; i < numPixels; i++, pixel += 4) { pixel[CR] = *in++; pixel[CG] = *in++; @@ -865,8 +724,7 @@ boolean GL_TexImage(dgltexformat_t format, DGLuint palid, loadFormat = GL_RGB; for(i = 0, pixel = buffer; i < numPixels; i++, pixel += 3) { - const byte* src = - &pal->data[MIN_OF(bdata[i], pal->num) * 3]; + const uint8_t* src = &pal->data[MIN_OF(bdata[i], pal->num) * 3]; pixel[CR] = gammaTable[src[CR]]; pixel[CG] = gammaTable[src[CG]]; @@ -880,8 +738,7 @@ boolean GL_TexImage(dgltexformat_t format, DGLuint palid, for(i = 0, pixel = buffer; i < numPixels; i++, pixel += 4) { - const byte* src = - &pal->data[MIN_OF(bdata[i], pal->num) * 3]; + const uint8_t* src = &pal->data[MIN_OF(bdata[i], pal->num) * 3]; pixel[CR] = gammaTable[src[CR]]; pixel[CG] = gammaTable[src[CG]]; @@ -906,7 +763,7 @@ boolean GL_TexImage(dgltexformat_t format, DGLuint palid, break; default: - M_Free(buffer); + free(buffer); Con_Error("LoadTexture: Unknown format %x.\n", format); break; } @@ -924,7 +781,7 @@ boolean GL_TexImage(dgltexformat_t format, DGLuint palid, } if(needFree) - M_Free(buffer); + free(buffer); } #ifdef _DEBUG diff --git a/doomsday/engine/portable/src/gl_main.c b/doomsday/engine/portable/src/gl_main.c index fea8578424..c956b71b04 100644 --- a/doomsday/engine/portable/src/gl_main.c +++ b/doomsday/engine/portable/src/gl_main.c @@ -984,6 +984,305 @@ void GL_LowRes(void) GL_TexReset(); } +int GL_NumMipmapLevels(int width, int height) +{ + int numLevels = 0; + while(width > 1 || height > 1) + { + width /= 2; + height /= 2; + ++numLevels; + } + return numLevels; +} + +uint8_t* GL_SmartFilter(int method, const uint8_t* src, int width, int height, + int flags, int* outWidth, int* outHeight) +{ + int newWidth, newHeight; + uint8_t* out = NULL; + + switch(method) + { + default: // linear interpolation. + newWidth = width * 2; + newHeight = height * 2; + out = GL_ScaleBuffer(src, width, height, 4, newWidth, newHeight); + break; + + case 1: // nearest neighbor. + newWidth = width * 2; + newHeight = height * 2; + out = GL_ScaleBufferNearest(src, width, height, 4, newWidth, newHeight); + break; + + case 2: // hq2x + newWidth = width * 2; + newHeight = height * 2; + out = GL_SmartFilterHQ2x(src, width, height, flags); + break; + }; + + if(NULL == out) + { // Unchanged, return the source image. + if(outWidth) *outWidth = width; + if(outHeight) *outHeight = height; + return (uint8_t*)src; + } + + if(outWidth) *outWidth = newWidth; + if(outHeight) *outHeight = newHeight; + return out; +} + +uint8_t* GL_ConvertBuffer(const uint8_t* in, int width, int height, int informat, + colorpaletteid_t palid, boolean gamma, int outformat) +{ + assert(in); + { + uint8_t* out; + + if(width <= 0 || height <= 0) + { + Con_Error("GL_ConvertBuffer: Attempt to convert zero-sized image."); + return (uint8_t*)in; + } + + if(informat == outformat) + { // No conversion necessary. + return (uint8_t*)in; + } + + if(0 == (out = malloc(width * height * outformat))) + Con_Error("GL_ConvertBuffer: Failed on allocation of %lu bytes for " + "conversion buffer.", (unsigned long) (width * height * outformat)); + + // Conversion from pal8(a) to RGB(A). + if(informat <= 2 && outformat >= 3) + { + GL_PalettizeImage(out, outformat, R_GetColorPalette(palid), gamma, + in, informat, width, height); + return out; + } + + // Conversion from RGB(A) to pal8(a), using pal18To8. + if(informat >= 3 && outformat <= 2) + { + GL_QuantizeImageToPalette(out, outformat, R_GetColorPalette(palid), + in, informat, width, height); + return out; + } + + if(informat == 3 && outformat == 4) + { + int i, numPixels = width * height; + int inSize = (informat == 2 ? 1 : informat); + int outSize = (outformat == 2 ? 1 : outformat); + + for(i = 0; i < numPixels; ++i, in += inSize, out += outSize) + { + memcpy(out, in, 3); + out[3] = 0xff; // Opaque. + } + } + return out; + } +} + +void GL_CalcLuminance(const uint8_t* buffer, int width, int height, int pixelSize, + colorpaletteid_t palid, float* brightX, float* brightY, float color[3], + float* lumSize) +{ + assert(buffer && brightX && brightY && color && lumSize); + { + DGLuint pal = (pixelSize == 1? R_GetColorPalette(palid) : 0); + const int limit = 0xc0, posLimit = 0xe0, colLimit = 0xc0; + int i, k, x, y, c, cnt = 0, posCnt = 0; + const uint8_t* src, *alphaSrc = NULL; + int avgCnt = 0, lowCnt = 0; + float average[3], lowAvg[3]; + uint8_t rgb[3]; + int region[4]; + + for(i = 0; i < 3; ++i) + { + average[i] = 0; + lowAvg[i] = 0; + } + src = buffer; + + if(pixelSize == 1) + { + // In paletted mode, the alpha channel follows the actual image. + alphaSrc = buffer + width * height; + } + + FindClipRegionNonAlpha(buffer, width, height, pixelSize, region); + if(region[2] > 0) + { + src += pixelSize * width * region[2]; + alphaSrc += width * region[2]; + } + (*brightX) = (*brightY) = 0; + + for(k = region[2], y = 0; k < region[3] + 1; ++k, ++y) + { + if(region[0] > 0) + { + src += pixelSize * region[0]; + alphaSrc += region[0]; + } + + for(i = region[0], x = 0; i < region[1] + 1; + ++i, ++x, src += pixelSize, alphaSrc++) + { + // Alpha pixels don't count. + if(pixelSize == 1) + { + if(*alphaSrc < 255) + continue; + } + else if(pixelSize == 4) + { + if(src[3] < 255) + continue; + } + + // Bright enough? + if(pixelSize == 1) + { + GL_GetColorPaletteRGB(pal, (DGLubyte*) rgb, *src); + } + else if(pixelSize >= 3) + { + memcpy(rgb, src, 3); + } + + if(rgb[0] > posLimit || rgb[1] > posLimit || rgb[2] > posLimit) + { + // This pixel will participate in calculating the average + // center point. + (*brightX) += x; + (*brightY) += y; + posCnt++; + } + + // Bright enough to affect size? + if(rgb[0] > limit || rgb[1] > limit || rgb[2] > limit) + cnt++; + + // How about the color of the light? + if(rgb[0] > colLimit || rgb[1] > colLimit || rgb[2] > colLimit) + { + avgCnt++; + for(c = 0; c < 3; ++c) + average[c] += rgb[c] / 255.f; + } + else + { + lowCnt++; + for(c = 0; c < 3; ++c) + lowAvg[c] += rgb[c] / 255.f; + } + } + + if(region[1] < width - 1) + { + src += pixelSize * (width - 1 - region[1]); + alphaSrc += (width - 1 - region[1]); + } + } + + if(!posCnt) + { + (*brightX) = region[0] + ((region[1] - region[0]) / 2.0f); + (*brightY) = region[2] + ((region[3] - region[2]) / 2.0f); + } + else + { + // Get the average. + (*brightX) /= posCnt; + (*brightY) /= posCnt; + // Add the origin offset. + (*brightX) += region[0]; + (*brightY) += region[2]; + } + + // Center on the middle of the brightest pixel. + (*brightX) += .5f; + (*brightY) += .5f; + + // The color. + if(!avgCnt) + { + if(!lowCnt) + { + // Doesn't the thing have any pixels??? Use white light. + for(c = 0; c < 3; ++c) + color[c] = 1; + } + else + { + // Low-intensity color average. + for(c = 0; c < 3; ++c) + color[c] = lowAvg[c] / lowCnt; + } + } + else + { + // High-intensity color average. + for(c = 0; c < 3; ++c) + color[c] = average[c] / avgCnt; + } + +/*#ifdef _DEBUG + Con_Message("GL_CalcLuminance: width %dpx, height %dpx, bits %d\n" + " cell region X[%d, %d] Y[%d, %d]\n" + " flare X= %g Y=%g %s\n" + " flare RGB[%g, %g, %g] %s\n", + width, height, pixelSize, + region[0], region[1], region[2], region[3], + (*brightX), (*brightY), + (posCnt? "(average)" : "(center)"), + color[0], color[1], color[2], + (avgCnt? "(hi-intensity avg)" : + lowCnt? "(low-intensity avg)" : "(white light)")); +#endif*/ + + // AmplifyLuma color. + amplify(color); + + // How about the size of the light source? + *lumSize = MIN_OF(((2 * cnt + avgCnt) / 3.0f / 70.0f), 1); + } +} + +int GL_ChooseSmartFilter(int width, int height, int flags) +{ + if(width >= MINTEXWIDTH && height >= MINTEXHEIGHT) + return 2; // hq2x + return 1; // nearest neighbor. +} + +void amplify(float rgb[3]) +{ + float max = 0; + int i; + + for(i = 0; i < 3; ++i) + if(rgb[i] > max) + max = rgb[i]; + + if(!max || max == 1) + return; + + if(max) + { + for(i = 0; i < 3; ++i) + rgb[i] = rgb[i] / max; + } +} + /** * Change graphics mode resolution. */ diff --git a/doomsday/engine/portable/src/gl_tex.c b/doomsday/engine/portable/src/gl_tex.c index fb7d7c632e..b6974dfb62 100644 --- a/doomsday/engine/portable/src/gl_tex.c +++ b/doomsday/engine/portable/src/gl_tex.c @@ -22,12 +22,6 @@ * Boston, MA 02110-1301 USA */ -/** - * Image manipulation algorithms. - */ - -// HEADER FILES ------------------------------------------------------------ - #include #include #include @@ -35,37 +29,17 @@ #include "de_base.h" #include "de_console.h" #include "de_refresh.h" -#include "de_graphics.h" -#include "de_misc.h" - -#include "image.h" - -// MACROS ------------------------------------------------------------------ - -// TYPES ------------------------------------------------------------------- +//#include "de_graphics.h" +//#include "de_misc.h" -// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- - -// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- - -// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- - -// EXTERNAL DATA DECLARATIONS ---------------------------------------------- - -// PUBLIC DATA DEFINITIONS ------------------------------------------------- - -// PRIVATE DATA DEFINITIONS ------------------------------------------------ - -static byte *scratchBuffer = NULL; +static uint8_t* scratchBuffer = NULL; static size_t scratchBufferSize = 0; -// CODE -------------------------------------------------------------------- - /** * Provides a persistent scratch buffer for use by texture manipulation * routines e.g. scaleLine(). */ -static byte *GetScratchBuffer(size_t size) +static uint8_t* GetScratchBuffer(size_t size) { // Need to enlarge? if(size > scratchBufferSize) @@ -73,79 +47,9 @@ static byte *GetScratchBuffer(size_t size) scratchBuffer = Z_Realloc(scratchBuffer, size, PU_APPSTATIC); scratchBufferSize = size; } - return scratchBuffer; } -/** - * Copies a rectangular region of the source buffer to the destination - * buffer. Doesn't perform clipping, so be careful. - * Yeah, 13 parameters... - */ -void pixBlt(byte *src, int srcWidth, int srcHeight, byte *dest, int destWidth, - int destHeight, int alpha, int srcRegX, int srcRegY, int destRegX, - int destRegY, int regWidth, int regHeight) -{ - int y; // Y in the copy region. - int srcNumPels = srcWidth * srcHeight; - int destNumPels = destWidth * destHeight; - - for(y = 0; y < regHeight; ++y) // Copy line by line. - { - // The color index data. - memcpy(dest + destRegX + (y + destRegY) * destWidth, - src + srcRegX + (y + srcRegY) * srcWidth, regWidth); - - if(alpha) - { - // Alpha channel data. - memcpy(dest + destNumPels + destRegX + (y + destRegY) * destWidth, - src + srcNumPels + srcRegX + (y + srcRegY) * srcWidth, - regWidth); - } - } -} - -void GL_ConvertBuffer(int width, int height, int informat, int outformat, - const uint8_t* in, uint8_t* out, colorpaletteid_t palid, boolean gamma) -{ - if(informat == outformat) - { - // No conversion necessary. - memcpy(out, in, width * height * informat); - return; - } - - // Conversion from pal8(a) to RGB(A). - if(informat <= 2 && outformat >= 3) - { - GL_PalettizeImage(out, outformat, R_GetColorPalette(palid), gamma, - in, informat, width, height); - return; - } - - // Conversion from RGB(A) to pal8(a), using pal18To8. - if(informat >= 3 && outformat <= 2) - { - GL_QuantizeImageToPalette(out, outformat, R_GetColorPalette(palid), - in, informat, width, height); - return; - } - - if(informat == 3 && outformat == 4) - { - int i, numPixels = width * height; - int inSize = (informat == 2 ? 1 : informat); - int outSize = (outformat == 2 ? 1 : outformat); - - for(i = 0; i < numPixels; ++i, in += inSize, out += outSize) - { - memcpy(out, in, 3); - out[3] = 0xff; // Opaque. - } - } -} - /** * Len is measured in out units. Comps is the number of components per * pixel, or rather the number of bytes per pixel (3 or 4). The strides must @@ -179,17 +83,19 @@ static void scaleLine(const uint8_t* in, int inStride, uint8_t* out, int outStri weight = inPos & 0xffff; invWeight = 0x10000 - weight; - out[0] = (byte)((col1[0] * invWeight + col2[0] * weight) >> 16); - out[1] = (byte)((col1[1] * invWeight + col2[1] * weight) >> 16); - out[2] = (byte)((col1[2] * invWeight + col2[2] * weight) >> 16); + out[0] = (uint8_t)((col1[0] * invWeight + col2[0] * weight) >> 16); + out[1] = (uint8_t)((col1[1] * invWeight + col2[1] * weight) >> 16); + out[2] = (uint8_t)((col1[2] * invWeight + col2[2] * weight) >> 16); if(comps == 4) - out[3] = (byte)((col1[3] * invWeight + col2[3] * weight) >> 16); + out[3] = (uint8_t)((col1[3] * invWeight + col2[3] * weight) >> 16); } // The last pixel. memcpy(out, in + (inLen - 1) * inStride, comps); + return; } - else if(inToOutScale < 1) + + if(inToOutScale < 1) { // Minification needs to calculate the average of each of // the pixels contained by the out pixel. @@ -204,7 +110,7 @@ static void scaleLine(const uint8_t* in, int inStride, uint8_t* out, int outStri for(c = 0; c < comps; ++c) { - out[c] = (byte)(cumul[c] / count); + out[c] = (uint8_t)(cumul[c] / count); cumul[c] = 0; } count = 0; @@ -217,56 +123,65 @@ static void scaleLine(const uint8_t* in, int inStride, uint8_t* out, int outStri // Fill in the last pixel, too. if(count) for(c = 0; c < comps; ++c) - out[c] = (byte)(cumul[c] / count); + out[c] = (uint8_t)(cumul[c] / count); + return; } - else + + // No need for scaling. + if(comps == 3) { - // No need for scaling. - if(comps == 3) + for(i = outLen; i > 0; --i, out += outStride, in += inStride) { - for(i = outLen; i > 0; --i, out += outStride, in += inStride) - { - out[0] = in[0]; - out[1] = in[1]; - out[2] = in[2]; - } + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; } - else if(comps == 4) + } + else if(comps == 4) + { + for(i = outLen; i > 0; i--, out += outStride, in += inStride) { - for(i = outLen; i > 0; i--, out += outStride, in += inStride) - { - out[0] = in[0]; - out[1] = in[1]; - out[2] = in[2]; - out[3] = in[3]; - } + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = in[3]; } } } /// \todo Avoid use of a secondary buffer by scaling directly to output. -uint8_t* GL_ScaleBuffer32(const uint8_t* in, int inWidth, int inHeight, int comps, +uint8_t* GL_ScaleBuffer(const uint8_t* in, int width, int height, int comps, int outWidth, int outHeight) { - uint8_t* outOff, *buffer = GetScratchBuffer(comps * outWidth * inHeight); + assert(in); + { uint inOffsetSize, outOffsetSize; + uint8_t* outOff, *buffer; const uint8_t* inOff; - int stride; uint8_t* out; + int stride; + + if(width <= 0 || height <= 0) + return (uint8_t*)in; + + if(comps != 3 && comps != 4) + Con_Error("GL_ScaleBuffer: Attempted on non-rgb(a) image (comps=%i).", comps); + + buffer = GetScratchBuffer(comps * outWidth * height); if(0 == (out = malloc(comps * outWidth * outHeight))) - Con_Error("GL_ScaleBuffer32: Failed on allocation of %lu bytes for " + Con_Error("GL_ScaleBuffer: Failed on allocation of %lu bytes for " "output buffer.", (unsigned long) (comps * outWidth * outHeight)); // First scale horizontally, to outWidth, into the temporary buffer. inOff = in; outOff = buffer; - inOffsetSize = inWidth * comps; + inOffsetSize = width * comps; outOffsetSize = outWidth * comps; { int i; - for(i = 0; i < inHeight; ++i, inOff += inOffsetSize, outOff += outOffsetSize) + for(i = 0; i < height; ++i, inOff += inOffsetSize, outOff += outOffsetSize) { - scaleLine(inOff, comps, outOff, comps, outWidth, inWidth, comps); + scaleLine(inOff, comps, outOff, comps, outWidth, width, comps); }} // Then scale vertically, to outHeight, into the out buffer. @@ -278,249 +193,421 @@ uint8_t* GL_ScaleBuffer32(const uint8_t* in, int inWidth, int inHeight, int comp { int i; for(i = 0; i < outWidth; ++i, inOff += inOffsetSize, outOff += outOffsetSize) { - scaleLine(inOff, stride, outOff, stride, outHeight, inHeight, comps); + scaleLine(inOff, stride, outOff, stride, outHeight, height, comps); }} return out; + } } -/** - * Works within the given data, reducing the size of the picture to half - * its original. Width and height must be powers of two. - */ -void GL_DownMipmap32(byte *in, int width, int height, int comps) +uint8_t* GL_ScaleBufferNearest(const uint8_t* in, int width, int height, int comps, + int outWidth, int outHeight) +{ + assert(in); + { + int ratioX, ratioY, shearY; + uint8_t* out, *outP; + + if(width <= 0 || height <= 0) + return (uint8_t*)in; + + ratioX = (int)(width << 16) / outWidth + 1; + ratioY = (int)(height << 16) / outHeight + 1; + + if(NULL == (out = malloc(comps * outWidth * outHeight))) + Con_Error("GL_ScaleBufferNearest: Failed on allocation of %lu bytes for " + "output buffer.", (unsigned long) (comps * outWidth * outHeight)); + + outP = out; + shearY = 0; + { int i; + for(i = 0; i < outHeight; ++i, shearY += ratioY) + { + int shearX = 0; + int shearY2 = (shearY >> 16) * width; + { int j; + for(j = 0; j < outWidth; ++j, outP += comps, shearX += ratioX) + { + int c, n = (shearY2 + (shearX >> 16)) * comps; + for(c = 0; c < comps; ++c, n++) + outP[c] = in[n]; + }} + }} + return out; + } +} + +void GL_DownMipmap32(uint8_t* in, int width, int height, int comps) { - byte *out; - int x, y, c, outW = width >> 1, outH = height >> 1; + assert(in); + { + int x, y, c, outW = width >> 1, outH = height >> 1; + uint8_t* out; + + if(width <= 0 || height <= 0 || comps <= 0) + return; if(width == 1 && height == 1) { #if _DEBUG - Con_Error("GL_DownMipmap32 can't be called for a 1x1 image.\n"); + Con_Error("GL_DownMipmap32: Can't be called for a 1x1 image.\n"); #endif return; } - if(!outW || !outH) // Limited, 1x2|2x1 -> 1x1 reduction? + // Limited, 1x2|2x1 -> 1x1 reduction? + if(!outW || !outH) { - int outDim = (width > 1 ? outW : outH); + int outDim = (width > 1 ? outW : outH); out = in; for(x = 0; x < outDim; ++x, in += comps * 2) for(c = 0; c < comps; ++c, out++) - *out = (byte)((in[c] + in[comps + c]) >> 1); + *out = (uint8_t)((in[c] + in[comps + c]) >> 1); + return; } - else // Unconstrained, 2x2 -> 1x1 reduction? - { - out = in; - for(y = 0; y < outH; ++y, in += width * comps) - for(x = 0; x < outW; ++x, in += comps * 2) - for(c = 0; c < comps; ++c, out++) - *out = - (byte)((in[c] + in[comps + c] + in[comps * width + c] + - in[comps * (width + 1) + c]) >> 2); + + // Unconstrained, 2x2 -> 1x1 reduction? + out = in; + for(y = 0; y < outH; ++y, in += width * comps) + for(x = 0; x < outW; ++x, in += comps * 2) + for(c = 0; c < comps; ++c, out++) + *out = (uint8_t)((in[c] + in[comps + c] + in[comps * width + c] + + in[comps * (width + 1) + c]) >> 2); } } -/** - * Determine the optimal size for a texture. Usually the dimensions are - * scaled upwards to the next power of two. - * - * @param noStretch If @c true, the stretching can be skipped. - * @param isMipMapped If @c true, we will require mipmaps (this has - * an affect on the optimal size). - */ -boolean GL_OptimalSize(int width, int height, int *optWidth, int *optHeight, - boolean noStretch, boolean isMipMapped) +void GL_DownMipmap8(uint8_t* in, uint8_t* fadedOut, int width, int height, float fade) { - if(GL_state.textureNonPow2 && !isMipMapped) + int x, y, outW = width / 2, outH = height / 2; + float invFade; + byte* out = in; + + if(fade > 1) + fade = 1; + invFade = 1 - fade; + + if(width == 1 && height == 1) { - *optWidth = width; - *optHeight = height; +#if _DEBUG + Con_Error("GL_DownMipmap8: Can't be called for a 1x1 image.\n"); +#endif + return; } - else if(noStretch) - { - *optWidth = M_CeilPow2(width); - *optHeight = M_CeilPow2(height); - // MaxTexSize may prevent using noStretch. - if(*optWidth > GL_state.maxTexSize || - *optHeight > GL_state.maxTexSize) + if(!outW || !outH) + { // Limited, 1x2|2x1 -> 1x1 reduction? + int outDim = (width > 1 ? outW : outH); + + for(x = 0; x < outDim; x++, in += 2) { - noStretch = false; + *out = (in[0] + in[1]) / 2; + *fadedOut++ = (byte) (*out * invFade + 0x80 * fade); + out++; } } else - { - // Determine the most favorable size for the texture. - if(texQuality == TEXQ_BEST) // The best quality. - { - // At the best texture quality *opt, all textures are - // sized *upwards*, so no details are lost. This takes - // more memory, but naturally looks better. - *optWidth = M_CeilPow2(width); - *optHeight = M_CeilPow2(height); - } - else if(texQuality == 0) - { - // At the lowest quality, all textures are sized down - // to the nearest power of 2. - *optWidth = M_FloorPow2(width); - *optHeight = M_FloorPow2(height); - } - else - { - // At the other quality *opts, a weighted rounding - // is used. - *optWidth = M_WeightPow2(width, 1 - texQuality / (float) TEXQ_BEST); - *optHeight = - M_WeightPow2(height, 1 - texQuality / (float) TEXQ_BEST); - } + { // Unconstrained, 2x2 -> 1x1 reduction? + for(y = 0; y < outH; y++, in += width) + for(x = 0; x < outW; x++, in += 2) + { + *out = (in[0] + in[1] + in[width] + in[width + 1]) / 4; + *fadedOut++ = (byte) (*out * invFade + 0x80 * fade); + out++; + } } +} - // Hardware limitations may force us to modify the preferred - // texture size. - if(*optWidth > GL_state.maxTexSize) - *optWidth = GL_state.maxTexSize; - if(*optHeight > GL_state.maxTexSize) - *optHeight = GL_state.maxTexSize; +void FindAverageLineColorIdx(const uint8_t* data, int w, int h, int line, + colorpaletteid_t palid, boolean hasAlpha, float col[3]) +{ + assert(data && col); + { + long count, numpels, avg[3] = { 0, 0, 0 }; + const uint8_t* start, *alphaStart; + DGLubyte rgbUBV[3]; + DGLuint pal; - // Some GL drivers seem to have problems with VERY small textures. - if(*optWidth < MINTEXWIDTH) - *optWidth = MINTEXWIDTH; - if(*optHeight < MINTEXHEIGHT) - *optHeight = MINTEXHEIGHT; + if(w <= 0 || h <= 0) + { + col[CR] = col[CG] = col[CB] = 0; + return; + } - if(ratioLimit) + if(line >= h) { - if(*optWidth > *optHeight) // Wide texture. - { - if(*optHeight < *optWidth / ratioLimit) - *optHeight = *optWidth / ratioLimit; - } - else // Tall texture. +#if _DEBUG + Con_Error("FindAverageLineColorIdx: Attempted to average outside valid area " + "(height=%i line=%i).", h, line); +#endif + col[CR] = col[CG] = col[CB] = 0; + return; + } + + numpels = w * h; + start = data + w * line; + alphaStart = data + numpels + w * line; + pal = R_GetColorPalette(palid); + count = 0; + { long i; + for(i = 0; i < w; ++i) + { + if(!hasAlpha || alphaStart[i]) { - if(*optWidth < *optHeight / ratioLimit) - *optWidth = *optHeight / ratioLimit; + GL_GetColorPaletteRGB(pal, rgbUBV, start[i]); + avg[CR] += rgbUBV[CR]; + avg[CG] += rgbUBV[CG]; + avg[CB] += rgbUBV[CB]; + ++count; } - } + }} + // All transparent? Sorry... + if(!count) + return; - return noStretch; + col[CR] = avg[CR] / count * reciprocal255; + col[CG] = avg[CG] / count * reciprocal255; + col[CB] = avg[CB] / count * reciprocal255; + } } -/** - * Converts the image data to grayscale luminance in-place. - */ -void GL_ConvertToLuminance(image_t* image) +void FindAverageLineColor(const uint8_t* pixels, int width, int height, + int pixelSize, int line, float col[3]) { - int p, total = image->width * image->height; - byte* alphaChannel = NULL; - byte* ptr = image->pixels; + assert(pixels && col); + { + long avg[3] = { 0, 0, 0 }; + const uint8_t* src; - if(image->pixelSize < 3) - { // No need to convert anything. + if(width <= 0 || height <= 0) + { + col[CR] = col[CG] = col[CB] = 0; return; } - // Do we need to relocate the alpha data? - if(image->pixelSize == 4) - { // Yes. Take a copy. - alphaChannel = malloc(total); - ptr = image->pixels; - for(p = 0; p < total; ++p, ptr += image->pixelSize) - alphaChannel[p] = ptr[3]; + if(line >= height) + { +#if _DEBUG + Con_Error("FindAverageLineColor: Attempted to average outside valid area " + "(height=%i line=%i).", height, line); +#endif + col[CR] = col[CG] = col[CB] = 0; + return; } - // Average the RGB colors. - ptr = image->pixels; - for(p = 0; p < total; ++p, ptr += image->pixelSize) + src = pixels + pixelSize * width * line; + { int i; + for(i = 0; i < width; ++i, src += pixelSize) { - int min = MIN_OF(ptr[0], MIN_OF(ptr[1], ptr[2])); - int max = MAX_OF(ptr[0], MAX_OF(ptr[1], ptr[2])); - image->pixels[p] = (min + max) / 2; + avg[CR] += src[CR]; + avg[CG] += src[CG]; + avg[CB] += src[CB]; + }} + + col[CR] = avg[CR] / width * reciprocal255; + col[CG] = avg[CG] / width * reciprocal255; + col[CB] = avg[CB] / width * reciprocal255; } +} - // Do we need to relocate the alpha data? - if(alphaChannel) +void FindAverageColor(const uint8_t* pixels, int width, int height, + int pixelSize, float col[3]) +{ + assert(pixels && col); + { + long numpels, avg[3] = { 0, 0, 0 }; + const uint8_t* src; + + if(width <= 0 || height <= 0) { - memcpy(image->pixels + total, alphaChannel, total); - image->pixelSize = 2; - free(alphaChannel); + col[CR] = col[CG] = col[CB] = 0; return; } - image->pixelSize = 1; + if(pixelSize != 3 && pixelSize != 4) + { +#if _DEBUG + Con_Error("FindAverageColor: Attempted on non-rgb(a) image " + "(pixelSize=%i).", pixelSize); +#endif + col[CR] = col[CG] = col[CB] = 0; + return; + } + + numpels = width * height; + src = pixels; + { long i; + for(i = 0; i < numpels; ++i, src += pixelSize) + { + avg[CR] += src[CR]; + avg[CG] += src[CG]; + avg[CB] += src[CB]; + }} + + col[CR] = avg[CR] / numpels * reciprocal255; + col[CG] = avg[CG] / numpels * reciprocal255; + col[CB] = avg[CB] / numpels * reciprocal255; + } } -void GL_ConvertToAlpha(image_t *image, boolean makeWhite) +void FindAverageColorIdx(const uint8_t* data, int w, int h, + colorpaletteid_t palid, boolean hasAlpha, float col[3]) { - int p, total = image->width * image->height; + assert(data && col); + { + long numpels, count, avg[3] = { 0, 0, 0 }; + const uint8_t* alphaStart; + DGLubyte rgbUBV[3]; + DGLuint pal; - GL_ConvertToLuminance(image); - for(p = 0; p < total; ++p) + if(w <= 0 || h <= 0) { - // Move the average color to the alpha channel, make the - // actual color white. - image->pixels[total + p] = image->pixels[p]; - if(makeWhite) - image->pixels[p] = 255; + col[CR] = col[CG] = col[CB] = 0; + return; } - image->pixelSize = 2; + numpels = w * h; + alphaStart = data + numpels; + pal = R_GetColorPalette(palid); + count = 0; + { long i; + for(i = 0; i < numpels; ++i) + { + if(!hasAlpha || alphaStart[i]) + { + GL_GetColorPaletteRGB(pal, rgbUBV, data[i]); + avg[CR] += rgbUBV[CR]; + avg[CG] += rgbUBV[CG]; + avg[CB] += rgbUBV[CB]; + ++count; + } + }} + // All transparent? Sorry... + if(0 == count) + return; + + col[CR] = avg[CR] / count * reciprocal255; + col[CG] = avg[CG] / count * reciprocal255; + col[CB] = avg[CB] / count * reciprocal255; + } } -boolean ImageHasAlpha(image_t *img) +void FindClipRegionNonAlpha(const uint8_t* buffer, int width, int height, + int pixelsize, int retRegion[4]) { - uint i, size; - boolean hasAlpha = false; - byte *in; + assert(buffer && retRegion); + { + const uint8_t* src, *alphasrc; + int region[4]; - if(img->pixelSize == 4) + if(width <= 0 || height <= 0) { - size = img->width * img->height; - for(i = 0, in = img->pixels; i < size; ++i, in += 4) - if(in[3] < 255) +#if _DEBUG + Con_Error("FindClipRegionNonAlpha: Attempt to find region on zero-sized image."); +#endif + retRegion[0] = retRegion[1] = retRegion[2] = retRegion[3] = 0; + return; + } + + region[0] = width; + region[1] = 0; + region[2] = height; + region[3] = 0; + + src = buffer; + // For paletted images the alpha channel follows the actual image. + if(pixelsize == 1) + alphasrc = buffer + width * height; + else + alphasrc = NULL; + + // \todo This is not very efficent. Better to use an algorithm which works + // on full rows and full columns. + { int k, i; + for(k = 0; k < height; ++k) + for(i = 0; i < width; ++i, src += pixelsize, alphasrc++) + { + // Alpha pixels don't count. + if(pixelsize == 1) { - hasAlpha = true; - break; + if(*alphasrc < 255) + continue; + } + else if(pixelsize == 4) + { + if(src[3] < 255) + continue; } + + if(i < region[0]) + region[0] = i; + if(i > region[1]) + region[1] = i; + + if(k < region[2]) + region[2] = k; + if(k > region[3]) + region[3] = k; + } } - return hasAlpha; + memcpy(retRegion, region, sizeof(retRegion)); + } } -int LineAverageRGB(byte *imgdata, int width, int height, int line, - byte *rgb, byte *palette, boolean hasAlpha) +#if 0 +void BlackOutlines(uint8_t* pixels, int width, int height) { - byte *start = imgdata + width * line; - byte *alphaStart = start + width * height; - int i, c, count = 0; - int integerRGB[3] = { 0, 0, 0 }; - byte col[3]; + assert(pixels); + { + uint16_t* dark; + int x, y, a, b; + uint8_t* pix; + long numpels; + + if(width <= 0 || height <= 0) + return; - for(i = 0; i < width; ++i) + numpels = width * height; + dark = calloc(1, 2 * numpels); + + for(y = 1; y < height - 1; ++y) { - // Not transparent? - if(alphaStart[i] > 0 || !hasAlpha) + for(x = 1; x < width - 1; ++x) { - count++; - // Ignore the gamma level. - memcpy(col, palette + 3 * start[i], 3); - for(c = 0; c < 3; ++c) - integerRGB[c] += col[c]; + pix = pixels + (x + y * width) * 4; + if(pix[3] > 128) // Not transparent. + { + // Apply darkness to surrounding transparent pixels. + for(a = -1; a <= 1; ++a) + { + for(b = -1; b <= 1; ++b) + { + uint8_t* other = pix + (a + b * width) * 4; + if(other[3] < 128) // Transparent. + { + dark[(x + a) + (y + b) * width] += 40; + } + } + } + } } } - // All transparent? Sorry... - if(!count) - return 0; - // We're going to make it! - for(c = 0; c < 3; ++c) - rgb[c] = (byte) (integerRGB[c] / count); - return 1; // Successful. + // Apply the darkness. + { long i; + for(i = 0, pix = pixels; i < numpels; ++i, pix += 4) + { + pix[3] = MIN_OF(255, (int)(pix[3] + dark[a])); + }} + + // Release temporary storage. + free(dark); + } } +#endif -/// \fixme Not a very efficient algorithm... -void ColorOutlines(uint8_t* buffer, int width, int height) +/// \todo Not a very efficient algorithm... +void ColorOutlinesIdx(uint8_t* buffer, int width, int height) { assert(buffer); { @@ -569,393 +656,240 @@ void ColorOutlines(uint8_t* buffer, int width, int height) } } -/** - * The given RGB color is scaled uniformly so that the highest component - * becomes one. - */ -void amplify(float* rgb) +void EqualizeLuma(uint8_t* pixels, int width, int height, float* rBaMul, + float* rHiMul, float* rLoMul) { - float max = 0; - int i; - - for(i = 0; i < 3; ++i) - if(rgb[i] > max) - max = rgb[i]; + assert(pixels); + { + float hiMul, loMul, baMul; + long wideAvg, numpels; + uint8_t min, max, avg; + uint8_t* pix; - if(!max || max == 1) + if(width <= 0 || height <= 0) return; - if(max) - { - for(i = 0; i < 3; ++i) - rgb[i] = rgb[i] / max; - } -} - -/** - * Used by flares and dynamic lights. - */ -void averageColorIdx(rgbcol_t col, byte* data, int w, int h, - colorpaletteid_t palid, boolean hasAlpha) -{ - const int numpels = w * h; - byte* alphaStart = data + numpels; - DGLubyte rgbUBV[3]; - float r, g, b; - DGLuint pal = R_GetColorPalette(palid); - uint count; - int i; + numpels = width * height; + min = 255; + max = 0; + wideAvg = 0; - // First clear them. - for(i = 0; i < 3; ++i) - col[i] = 0; + { long i; + for(i = 0, pix = pixels; i < numpels; ++i, pix += 1) + { + if(*pix < min) min = *pix; + if(*pix > max) max = *pix; + wideAvg += *pix; + }} - r = g = b = count = 0; - for(i = 0; i < numpels; ++i) + if(max <= min || max == 0 || min == 255) { - if(!hasAlpha || alphaStart[i]) - { - count++; + if(rBaMul) *rBaMul = -1; + if(rHiMul) *rHiMul = -1; + if(rLoMul) *rLoMul = -1; + return; // Nothing we can do. + } - GL_GetColorPaletteRGB(pal, rgbUBV, data[i]); + avg = MIN_OF(255, wideAvg / numpels); - // Ignore the gamma level. - r += rgbUBV[CR] / 255.f; - g += rgbUBV[CG] / 255.f; - b += rgbUBV[CB] / 255.f; - } + // Allow a small margin of variance with the balance multiplier. + baMul = (!INRANGE_OF(avg, 127, 4)? (float)127/avg : 1); + if(baMul != 1) + { + if(max < 255) + max = (uint8_t) MINMAX_OF(1, (float)max - (255-max) * baMul, 255); + if(min > 0) + min = (uint8_t) MINMAX_OF(0, (float)min + min * baMul, 255); } - // All transparent? Sorry... - if(!count) - return; - col[0] = r / count; - col[1] = g / count; - col[2] = b / count; -} + hiMul = (max < 255? (float)255/max : 1); + loMul = (min > 0 ? 1-((float)min/255) : 1); -int lineAverageColorIdx(rgbcol_t col, byte* data, int w, int h, int line, - colorpaletteid_t palid, boolean hasAlpha) -{ - int i; - uint count; - const int numpels = w * h; - byte* start = data + w * line; - byte* alphaStart = data + numpels + w * line; - DGLubyte rgbUBV[3]; - float r, g, b; - DGLuint pal = R_GetColorPalette(palid); - - // First clear them. - for(i = 0; i < 3; ++i) - col[i] = 0; - - r = g = b = count = 0; - for(i = 0; i < w; ++i) + if(!(baMul == 1 && hiMul == 1 && loMul == 1)) { - if(!hasAlpha || alphaStart[i]) + long i; + for(i = 0, pix = pixels; i < numpels; ++i, pix += 1) { - count++; - - GL_GetColorPaletteRGB(pal, rgbUBV, start[i]); + // First balance. + *pix = (uint8_t) MINMAX_OF(0, ((float)*pix) * baMul, 255); - // Ignore the gamma level. - r += rgbUBV[CR] / 255.f; - g += rgbUBV[CG] / 255.f; - b += rgbUBV[CB] / 255.f; + // Now amplify. + if(*pix > 127) + *pix = (uint8_t) MINMAX_OF(0, ((float)*pix) * hiMul, 255); + else + *pix = (uint8_t) MINMAX_OF(0, ((float)*pix) * loMul, 255); } } - // All transparent? Sorry... - if(!count) - return 0; - - col[0] = r / count; - col[1] = g / count; - col[2] = b / count; - - return 1; // Successful. -} - -int lineAverageColorRGB(rgbcol_t col, byte* data, int w, int h, int line) -{ - int i; - float culmul[3]; - - for(i = 0; i < 3; ++i) - culmul[i] = 0; - *data += 3 * w * line; - for(i = 0; i < w; ++i) - { - culmul[CR] += (*data++) / 255.f; - culmul[CG] += (*data++) / 255.f; - culmul[CB] += (*data++) / 255.f; + if(rBaMul) *rBaMul = baMul; + if(rHiMul) *rHiMul = hiMul; + if(rLoMul) *rLoMul = loMul; } - - col[CR] = culmul[CR] / w; - col[CG] = culmul[CG] / w; - col[CB] = culmul[CB] / w; - - return 1; // Successful. } -void averageColorRGB(rgbcol_t col, byte *data, int w, int h) +void Desaturate(uint8_t* pixels, int width, int height, int comps) { - uint i; - const uint numpels = w * h; - float cumul[3]; + assert(pixels); + { + uint8_t* pix; + long i, numpels; - if(!numpels) + if(width <= 0 || height <= 0) return; - for(i = 0; i < 3; ++i) - cumul[i] = 0; - for(i = 0; i < numpels; ++i) + numpels = width * height; + for(i = 0, pix = pixels; i < numpels; ++i, pix += comps) { - cumul[0] += (*data++) / 255.f; - cumul[1] += (*data++) / 255.f; - cumul[2] += (*data++) / 255.f; + int min = MIN_OF(pix[CR], MIN_OF(pix[CG], pix[CB])); + int max = MAX_OF(pix[CR], MAX_OF(pix[CG], pix[CB])); + pix[CR] = pix[CG] = pix[CB] = (min + max) / 2; + } } - - for(i = 0; i < 3; ++i) - col[i] = cumul[i] / numpels; } -/** - * Calculates a clip region for the buffer that excludes alpha pixels. - * NOTE: Cross spread from bottom > top, right > left (inside out). - * - * @param buffer Image data to be processed. - * @param width Width of the src buffer. - * @param height Height of the src buffer. - * @param pixelsize Pixelsize of the src buffer. Handles 1 (==2), 3 and 4. - * - * @returnval region Ptr to int[4] to write the resultant region to. - */ -void GL_GetNonAlphaRegion(byte *buffer, int width, int height, int pixelsize, - int *region) +void AmplifyLuma(uint8_t* pixels, int width, int height, boolean hasAlpha) { - int k, i; - int myregion[4]; - byte *src = buffer; - byte *alphasrc = NULL; - - myregion[0] = width; - myregion[1] = 0; - myregion[2] = height; - myregion[3] = 0; + assert(pixels); + { + long numPels; + uint8_t max = 0; - if(pixelsize == 1) - // In paletted mode, the alpha channel follows the actual image. - alphasrc = buffer + width * height; + if(width <= 0 || height <= 0) + return; - // \todo This is not very efficent. Better to use an algorithm which works on full rows and full columns. - for(k = 0; k < height; ++k) - for(i = 0; i < width; ++i, src += pixelsize, alphasrc++) + numPels = width * height; + if(hasAlpha) + { + uint8_t* pix = pixels; + uint8_t* apix = pixels + numPels; + long i; + for(i = 0; i < numPels; ++i, pix++, apix++) { - // Alpha pixels don't count. - if(pixelsize == 1) - { - if(*alphasrc < 255) - continue; - } - else if(pixelsize == 4) - { - if(src[3] < 255) - continue; - } - - if(i < myregion[0]) - myregion[0] = i; - if(i > myregion[1]) - myregion[1] = i; + // Only non-masked pixels count. + if(!(*apix > 0)) + continue; - if(k < myregion[2]) - myregion[2] = k; - if(k > myregion[3]) - myregion[3] = k; + if(*pix > max) + max = *pix; } - - memcpy(region, myregion, sizeof(myregion)); -} - -/** - * Calculates the properties of a dynamic light that the given sprite frame - * casts. Crop a boundary around the image to remove excess alpha'd pixels - * from adversely affecting the calculation. - * Handles pixel sizes; 1 (==2), 3 and 4. - */ -void GL_CalcLuminance(byte* buffer, int width, int height, int pixelSize, - colorpaletteid_t palid, float* brightX, - float* brightY, rgbcol_t* color, float* lumSize) -{ - DGLuint pal = - (pixelSize == 1? R_GetColorPalette(palid) : 0); - int i, k, x, y, c, cnt = 0, posCnt = 0; - byte rgb[3], *src, *alphaSrc = NULL; - int limit = 0xc0, posLimit = 0xe0, colLimit = 0xc0; - int avgCnt = 0, lowCnt = 0; - float average[3], lowAvg[3]; - int region[4]; - - for(i = 0; i < 3; ++i) - { - average[i] = 0; - lowAvg[i] = 0; } - src = buffer; - - if(pixelSize == 1) + else { - // In paletted mode, the alpha channel follows the actual image. - alphaSrc = buffer + width * height; + uint8_t* pix = pixels; + long i; + for(i = 0; i < numPels; ++i, pix++) + { + if(*pix > max) + max = *pix; + } } - GL_GetNonAlphaRegion(buffer, width, height, pixelSize, ®ion[0]); - if(region[2] > 0) + if(0 == max || 255 == max) + return; + + { uint8_t* pix = pixels; + long i; + for(i = 0; i < numPels; ++i, pix++) { - src += pixelSize * width * region[2]; - alphaSrc += width * region[2]; + *pix = (uint8_t) MINMAX_OF(0, (float)*pix / max * 255, 255); + }} } - (*brightX) = (*brightY) = 0; +} - for(k = region[2], y = 0; k < region[3] + 1; ++k, ++y) +void EnhanceContrast(uint8_t* pixels, int width, int height, int comps) +{ + assert(pixels); { - if(region[0] > 0) - { - src += pixelSize * region[0]; - alphaSrc += region[0]; - } + uint8_t* pix; + long i, numpels; - for(i = region[0], x = 0; i < region[1] + 1; - ++i, ++x, src += pixelSize, alphaSrc++) - { - // Alpha pixels don't count. - if(pixelSize == 1) - { - if(*alphaSrc < 255) - continue; - } - else if(pixelSize == 4) - { - if(src[3] < 255) - continue; - } - - // Bright enough? - if(pixelSize == 1) - { - GL_GetColorPaletteRGB(pal, (DGLubyte*) rgb, *src); - } - else if(pixelSize >= 3) - { - memcpy(rgb, src, 3); - } - - if(rgb[0] > posLimit || rgb[1] > posLimit || rgb[2] > posLimit) - { - // This pixel will participate in calculating the average - // center point. - (*brightX) += x; - (*brightY) += y; - posCnt++; - } + if(width <= 0 || height <= 0) + return; - // Bright enough to affect size? - if(rgb[0] > limit || rgb[1] > limit || rgb[2] > limit) - cnt++; + if(comps != 3 && comps != 4) + { +#if _DEBUG + Con_Error("EnhanceContrast: Attempted on non-rgb(a) image (comps=%i).", comps); +#endif + return; + } - // How about the color of the light? - if(rgb[0] > colLimit || rgb[1] > colLimit || rgb[2] > colLimit) - { - avgCnt++; - for(c = 0; c < 3; ++c) - average[c] += rgb[c] / 255.f; - } - else - { - lowCnt++; - for(c = 0; c < 3; ++c) - lowAvg[c] += rgb[c] / 255.f; - } - } + pix = pixels; + numpels = width * height; - if(region[1] < width - 1) + for(i = 0; i < numpels; ++i, pix += comps) + { + int c; + for(c = 0; c < 3; ++c) { - src += pixelSize * (width - 1 - region[1]); - alphaSrc += (width - 1 - region[1]); + uint8_t out; + if(pix[c] < 60) // Darken dark parts. + out = (uint8_t) MINMAX_OF(0, ((float)pix[c] - 70) * 1.0125f + 70, 255); + else if(pix[c] > 185) // Lighten light parts. + out = (uint8_t) MINMAX_OF(0, ((float)pix[c] - 185) * 1.0125f + 185, 255); + else + out = pix[c]; + pix[c] = out; } } + } +} - if(!posCnt) +void SharpenPixels(uint8_t* pixels, int width, int height, int comps) +{ + assert(pixels); { - (*brightX) = region[0] + ((region[1] - region[0]) / 2.0f); - (*brightY) = region[2] + ((region[3] - region[2]) / 2.0f); - } - else + const float strength = .05f; + uint8_t* result; + float A, B, C; + int x, y; + + if(width <= 0 || height <= 0) + return; + + if(comps != 3 && comps != 4) { - // Get the average. - (*brightX) /= posCnt; - (*brightY) /= posCnt; - // Add the origin offset. - (*brightX) += region[0]; - (*brightY) += region[2]; +#if _DEBUG + Con_Error("EnhanceContrast: Attempted on non-rgb(a) image (comps=%i).", comps); +#endif + return; } - // Center on the middle of the brightest pixel. - (*brightX) += .5f; - (*brightY) += .5f; + result = calloc(1, comps * width * height); - // The color. - if(!avgCnt) - { - if(!lowCnt) - { - // Doesn't the thing have any pixels??? Use white light. - for(c = 0; c < 3; ++c) - (*color)[c] = 1; - } - else + A = strength; + B = .70710678 * strength; // 1/sqrt(2) + C = 1 + 4*A + 4*B; + + for(y = 1; y < height - 1; ++y) + for(x = 1; x < width -1; ++x) { - // Low-intensity color average. + const uint8_t* pix = pixels + (x + y*width) * comps; + uint8_t* out = result + (x + y*width) * comps; + int c; for(c = 0; c < 3; ++c) - (*color)[c] = lowAvg[c] / lowCnt; + { + int r = (C*pix[c] - A*pix[c - width] - A*pix[c + comps] - A*pix[c - comps] - + A*pix[c + width] - B*pix[c + comps - width] - B*pix[c + comps + width] - + B*pix[c - comps - width] - B*pix[c - comps + width]); + out[c] = MINMAX_OF(0, r, 255); + } + + if(comps == 4) + out[3] = pix[3]; } - } - else - { - // High-intensity color average. - for(c = 0; c < 3; ++c) - (*color)[c] = average[c] / avgCnt; - } -/*#ifdef _DEBUG - Con_Message("GL_CalcLuminance: width %dpx, height %dpx, bits %d\n" - " cell region X[%d, %d] Y[%d, %d]\n" - " flare X= %g Y=%g %s\n" - " flare RGB[%g, %g, %g] %s\n", - width, height, pixelSize, - region[0], region[1], region[2], region[3], - (*brightX), (*brightY), - (posCnt? "(average)" : "(center)"), - (*color)[0], (*color)[1], (*color)[2], - (avgCnt? "(hi-intensity avg)" : - lowCnt? "(low-intensity avg)" : "(white light)")); -#endif*/ - - // Amplify color. - amplify(*color); - - // How about the size of the light source? - *lumSize = MIN_OF(((2 * cnt + avgCnt) / 3.0f / 70.0f), 1); + memcpy(pixels, result, comps * width * height); + free(result); + } } /** - * @return @c true, if the given color is either - * (0,255,255) or (255,0,255). + * @return @c true, if the given color is either (0,255,255) or (255,0,255). */ -static boolean ColorKey(byte *color) +static __inline boolean ColorKey(uint8_t* color) { + assert(color); return color[CB] == 0xff && ((color[CR] == 0xff && color[CG] == 0) || (color[CR] == 0 && color[CG] == 0xff)); } @@ -963,130 +897,52 @@ static boolean ColorKey(byte *color) /** * Buffer must be RGBA. Doesn't touch the non-keyed pixels. */ -static void DoColorKeying(byte *rgbaBuf, unsigned int width) +static void DoColorKeying(uint8_t* rgbaBuf, int width) { - uint i; - + assert(rgbaBuf); + { int i; for(i = 0; i < width; ++i, rgbaBuf += 4) - if(ColorKey(rgbaBuf)) - rgbaBuf[3] = rgbaBuf[2] = rgbaBuf[1] = rgbaBuf[0] = 0; + { + if(!ColorKey(rgbaBuf)) + continue; + rgbaBuf[3] = rgbaBuf[2] = rgbaBuf[1] = rgbaBuf[0] = 0; + }} } -/** - * Take the input buffer and convert to color keyed. A new buffer may be - * needed if the input buffer has three color components. - * - * @return If the in buffer wasn't large enough will return a - * ptr to the newly allocated buffer which must be - * freed with M_Free(). - */ -byte *GL_ApplyColorKeying(byte *buf, uint pixelSize, uint width, - uint height) +uint8_t* ApplyColorKeying(uint8_t* buf, int width, int height, int pixelSize) { - uint i; - byte *ckdest, *in, *out; - const uint numpels = width * height; + assert(buf); + + if(width <= 0 || height <= 0) + return buf; - // We must allocate a new buffer if the loaded image has three - // color components. + // We must allocate a new buffer if the loaded image has less than the + // required number of color components. if(pixelSize < 4) { - ckdest = M_Malloc(4 * width * height); - for(in = buf, out = ckdest, i = 0; i < numpels; - ++i, in += pixelSize, out += 4) + const long numpels = width * height; + uint8_t* ckdest = malloc(4 * numpels); + uint8_t* in, *out; + long i; + for(in = buf, out = ckdest, i = 0; i < numpels; ++i, in += pixelSize, out += 4) { if(ColorKey(in)) - memset(out, 0, 4); // Totally black. - else { - memcpy(out, in, 3); // The color itself. - out[CA] = 255; // Opaque. + memset(out, 0, 4); // Totally black. + continue; } - } + memcpy(out, in, 3); // The color itself. + out[CA] = 255; // Opaque. + } return ckdest; } - else // We can do the keying in-buffer. - { - // This preserves the alpha values of non-keyed pixels. - for(i = 0; i < height; ++i) - DoColorKeying(buf + 4 * i * width, height); - } - - return NULL; -} -uint8_t* GL_ScaleBufferNearest(const uint8_t* in, int width, int height, int comps, - int outWidth, int outHeight) -{ - int ratioX = (int)(width << 16) / outWidth + 1; - int ratioY = (int)(height << 16) / outHeight + 1; - int shearY = 0; - uint8_t* out, *outP; - - if(NULL == (out = malloc(comps * outWidth * outHeight))) - Con_Error("GL_ScaleBufferNearest: Failed on allocation of %lu bytes for " - "output buffer.", (unsigned long) (comps * outWidth * outHeight)); - - outP = out; + // We can do the keying in-buffer. + // This preserves the alpha values of non-keyed pixels. { int i; - for(i = 0; i < outHeight; ++i, shearY += ratioY) - { - int shearX = 0; - int shearY2 = (shearY >> 16) * width; - { int j; - for(j = 0; j < outWidth; ++j, outP += comps, shearX += ratioX) - { - int c, n = (shearY2 + (shearX >> 16)) * comps; - for(c = 0; c < comps; ++c, n++) - outP[c] = in[n]; - }} - }} - return out; -} - -int GL_ChooseSmartFilter(int width, int height, int flags) -{ - if(width >= MINTEXWIDTH && height >= MINTEXHEIGHT) - return 2; // hq2x - return 1; // nearest neighbor. -} - -uint8_t* GL_SmartFilter(int method, const uint8_t* src, int width, int height, - int flags, int* outWidth, int* outHeight) -{ - int newWidth, newHeight; - uint8_t* out = NULL; - - switch(method) - { - default: // linear interpolation. - newWidth = width * 2; - newHeight = height * 2; - out = GL_ScaleBuffer32(src, width, height, 4, newWidth, newHeight); - break; - - case 1: // nearest neighbor. - newWidth = width * 2; - newHeight = height * 2; - out = GL_ScaleBufferNearest(src, width, height, 4, newWidth, newHeight); - break; - - case 2: // hq2x - newWidth = width * 2; - newHeight = height * 2; - out = GL_SmartFilterHQ2x(src, width, height, flags); - break; - }; - - if(NULL == out) - { // Unchanged, return the source image. - if(outWidth) *outWidth = width; - if(outHeight) *outHeight = height; - return (uint8_t*)src; + for(i = 0; i < height; ++i) + DoColorKeying(buf + 4 * i * width, height); } - - if(outWidth) *outWidth = newWidth; - if(outHeight) *outHeight = newHeight; - return out; + return buf; } diff --git a/doomsday/engine/portable/src/gl_texmanager.c b/doomsday/engine/portable/src/gl_texmanager.c index 10692d7ea8..8d3d064e70 100644 --- a/doomsday/engine/portable/src/gl_texmanager.c +++ b/doomsday/engine/portable/src/gl_texmanager.c @@ -540,8 +540,8 @@ static byte* GL_LoadImageDFile(image_t* img, DFILE* file, const char* filePath) // How about some color-keying? if(GL_IsColorKeyed(filePath)) { - byte* out = GL_ApplyColorKeying(img->pixels, img->pixelSize, img->width, img->height); - if(out) + uint8_t* out = ApplyColorKeying(img->pixels, img->width, img->height, img->pixelSize); + if(out != img->pixels) { // Had to allocate a larger buffer, free the old and attach the new. free(img->pixels); @@ -553,7 +553,7 @@ static byte* GL_LoadImageDFile(image_t* img, DFILE* file, const char* filePath) } // Any alpha pixels? - if(ImageHasAlpha(img)) + if(GL_ImageHasAlpha(img)) img->flags |= IMGF_IS_MASKED; return img->pixels; @@ -654,7 +654,7 @@ DGLuint GL_UploadTexture2(texturecontent_t* content) // Calculate the real dimensions for the texture, as required by // the graphics hardware. - noStretch = GL_OptimalSize(width, height, &levelWidth, &levelHeight, noStretch, generateMipmaps); + noStretch = GL_OptimalSize(width, height, noStretch, generateMipmaps, &levelWidth, &levelHeight); // Get the RGB(A) version of the original texture. if(RGBData) @@ -666,14 +666,13 @@ DGLuint GL_UploadTexture2(texturecontent_t* content) else { // Convert a paletted source image to truecolor so it can be scaled. - // If there isn't an alpha channel yet, add one. - freeOriginal = true; + // If there isn't an alpha channel one will be added. comps = 4; - if(0 == (rgbaOriginal = malloc(width * height * comps))) - Con_Error("GL_UploadTexture2: Failed on allocation of %lu bytes for RGBA" - "conversion buffer.", (unsigned long) (width * height * comps)); - - GL_ConvertBuffer(width, height, alphaChannel ? 2 : 1, comps, data, rgbaOriginal, 0, !load8bit); + rgbaOriginal = GL_ConvertBuffer(data, width, height, alphaChannel ? 2 : 1, 0, !load8bit, comps); + if(rgbaOriginal != data) + { + freeOriginal = true; + } if(!alphaChannel) { @@ -702,17 +701,12 @@ DGLuint GL_UploadTexture2(texturecontent_t* content) if(comps == 3) { // Must convert to RGBA. - uint8_t* temp; - - if(0 == (temp = malloc(4 * width * height))) - Con_Error("GL_UploadTexture2: Failed on allocation of %lu bytes for" - "RGBA conversion buffer.", (unsigned long) (4 * width * height)); - - GL_ConvertBuffer(width, height, 3, 4, rgbaOriginal, temp, 0, !load8bit); - - if(freeOriginal) - free(rgbaOriginal); - + uint8_t* temp = GL_ConvertBuffer(rgbaOriginal, width, height, 3, 0, !load8bit, 4); + if(temp != rgbaOriginal) + { + if(freeOriginal) + free(rgbaOriginal); + } rgbaOriginal = temp; freeOriginal = true; comps = 4; @@ -720,7 +714,7 @@ DGLuint GL_UploadTexture2(texturecontent_t* content) } filtered = GL_SmartFilter(GL_ChooseSmartFilter(width, height, 0), rgbaOriginal, width, height, smartFlags, &width, &height); - noStretch = GL_OptimalSize(width, height, &levelWidth, &levelHeight, noStretch, generateMipmaps); + noStretch = GL_OptimalSize(width, height, noStretch, generateMipmaps, &levelWidth, &levelHeight); if(filtered != rgbaOriginal) { @@ -761,7 +755,7 @@ DGLuint GL_UploadTexture2(texturecontent_t* content) // Stretch to fit into power-of-two. if(width != levelWidth || height != levelHeight) { - buffer = GL_ScaleBuffer32(rgbaOriginal, width, height, comps, levelWidth, levelHeight); + buffer = GL_ScaleBuffer(rgbaOriginal, width, height, comps, levelWidth, levelHeight); if(buffer != rgbaOriginal) freeBuffer = true; } @@ -785,11 +779,6 @@ DGLuint GL_UploadTexture2(texturecontent_t* content) size_t outSize = levelWidth * levelHeight * (alphaChannel ? 2 : 1); int canGenMips = 0; - // Prepare the palette indices buffer, to be handed over to DGL. - if(0 == (idxBuffer = malloc(outSize))) - Con_Error("GL_UploadTexture2: Failed on allocation of %lu bytes for" - "8bit-palettized down-mip buffer.", (unsigned long) outSize); - // Since this is a paletted texture, we may need to manually // generate the mipmaps. for(i = 0; levelWidth || levelHeight; ++i) @@ -800,7 +789,7 @@ DGLuint GL_UploadTexture2(texturecontent_t* content) levelHeight = 1; // Convert to palette indices. - GL_ConvertBuffer(levelWidth, levelHeight, comps, alphaChannel ? 2 : 1, buffer, idxBuffer, content->palette, false); + idxBuffer = GL_ConvertBuffer(buffer, levelWidth, levelHeight, comps, content->palette, false, alphaChannel ? 2 : 1); // Upload it. if(!GL_TexImage(alphaChannel ? DGL_COLOR_INDEX_8_PLUS_A8 : DGL_COLOR_INDEX_8, pal, levelWidth, levelHeight, generateMipmaps && canGenMips ? true : generateMipmaps ? -i : false, idxBuffer)) @@ -808,6 +797,12 @@ DGLuint GL_UploadTexture2(texturecontent_t* content) Con_Error("GL_UploadTexture: TexImage failed (%i x %i) as 8-bit, alpha:%i\n", levelWidth, levelHeight, alphaChannel); } + if(idxBuffer != buffer) + { + free(idxBuffer); + idxBuffer = NULL; + } + // If no mipmaps need to generated, quit now. if(!generateMipmaps || canGenMips) break; @@ -819,8 +814,6 @@ DGLuint GL_UploadTexture2(texturecontent_t* content) levelWidth >>= 1; levelHeight >>= 1; } - - free(idxBuffer); } else { @@ -1216,7 +1209,7 @@ byte GL_LoadExtTexture(image_t* image, const char* name, gfxmode_t mode) { int newWidth = MIN_OF(image->width, GL_state.maxTexSize); int newHeight = MIN_OF(image->height, GL_state.maxTexSize); - uint8_t* scaledPixels = GL_ScaleBuffer32(image->pixels, image->width, image->height, image->pixelSize, newWidth, newHeight); + uint8_t* scaledPixels = GL_ScaleBuffer(image->pixels, image->width, image->height, image->pixelSize, newWidth, newHeight); if(scaledPixels != image->pixels) { free(image->pixels); @@ -2055,229 +2048,105 @@ DGLuint GL_GetFlareTexture(const dduri_t* uri, int oldIdx) } /** - * @param pixels RGBA data. Input read here, and output written here. - * @param width Width of the buffer. - * @param height Height of the buffer. + * Returns the OpenGL name of the texture. */ -static void BlackOutlines(byte* pixels, int width, int height) +DGLuint GL_PreparePatch(patchtex_t* patchTex) { - short* dark = M_Calloc(width * height * 2); - int x, y; - int a, b; - byte* pix; - - for(y = 1; y < height - 1; ++y) + if(patchTex) { - for(x = 1; x < width - 1; ++x) + material_load_params_t params; + const gltexture_inst_t* texInst; + memset(¶ms, 0, sizeof(params)); + if(patchTex->flags & PF_MONOCHROME) + params.tex.flags |= GLTF_MONOCHROME; + if(patchTex->flags & PF_UPSCALE_AND_SHARPEN) { - pix = pixels + (x + y * width) * 4; - if(pix[3] > 128) // Not transparent. - { - // Apply darkness to surrounding transparent pixels. - for(a = -1; a <= 1; ++a) - { - for(b = -1; b <= 1; ++b) - { - byte* other = pix + (a + b * width) * 4; - if(other[3] < 128) // Transparent. - { - dark[(x + a) + (y + b) * width] += 40; - } - } - } - } + params.tex.flags |= GLTF_UPSCALE_AND_SHARPEN; + params.tex.border = 1; } + if((texInst = GL_PrepareGLTexture(patchTex->texId, ¶ms, NULL))) + return texInst->id; } - - // Apply the darkness. - for(a = 0, pix = pixels; a < width * height; ++a, pix += 4) - { - uint c = pix[3] + dark[a]; - pix[3] = MIN_OF(255, c); - } - - // Release temporary storage. - M_Free(dark); + return 0; } -static void Equalize(byte* pixels, int width, int height, - float* rBaMul, float* rHiMul, float* rLoMul) +boolean GL_OptimalSize(int width, int height, boolean noStretch, boolean isMipMapped, + int* optWidth, int* optHeight) { - byte min = 255, max = 0; - int i, avg = 0; - float hiMul, loMul, baMul; - byte* pix; - - for(i = 0, pix = pixels; i < width * height; ++i, pix += 1) + assert(optWidth && optHeight); + if(GL_state.textureNonPow2 && !isMipMapped) { - if(*pix < min) min = *pix; - if(*pix > max) max = *pix; - avg += *pix; + *optWidth = width; + *optHeight = height; } - - if(max <= min || max == 0 || min == 255) + else if(noStretch) { - if(rBaMul) *rBaMul = -1; - if(rHiMul) *rHiMul = -1; - if(rLoMul) *rLoMul = -1; - return; // Nothing we can do. - } - - avg /= width * height; + *optWidth = M_CeilPow2(width); + *optHeight = M_CeilPow2(height); - // Allow a small margin of variance with the balance multiplier. - baMul = (!INRANGE_OF(avg, 127, 4)? (float)127/avg : 1); - if(baMul != 1) - { - if(max < 255) - max = (byte) MINMAX_OF(1, (float)max - (255-max) * baMul, 255); - if(min > 0) - min = (byte) MINMAX_OF(0, (float)min + min * baMul, 255); - } - - hiMul = (max < 255? (float)255/max : 1); - loMul = (min > 0 ? 1-((float)min/255) : 1); - - if(!(baMul == 1 && hiMul == 1 && loMul == 1)) - for(i = 0, pix = pixels; i < width * height; ++i, pix += 1) + // MaxTexSize may prevent using noStretch. + if(*optWidth > GL_state.maxTexSize || + *optHeight > GL_state.maxTexSize) { - // First balance. - *pix = (byte) MINMAX_OF(0, ((float)*pix) * baMul, 255); - - // Now amplify. - if(*pix > 127) - *pix = (byte) MINMAX_OF(0, ((float)*pix) * hiMul, 255); - else - *pix = (byte) MINMAX_OF(0, ((float)*pix) * loMul, 255); + noStretch = false; } - - if(rBaMul) *rBaMul = baMul; - if(rHiMul) *rHiMul = hiMul; - if(rLoMul) *rLoMul = loMul; -} - -static void Desaturate(byte* pixels, int width, int height, int comps) -{ - byte* pix; - int i; - - for(i = 0, pix = pixels; i < width * height; ++i, pix += comps) - { - int min = MIN_OF(pix[CR], MIN_OF(pix[CG], pix[CB])); - int max = MAX_OF(pix[CR], MAX_OF(pix[CG], pix[CB])); - pix[CR] = pix[CG] = pix[CB] = (min + max) / 2; } -} - -static void Amplify(byte* pixels, int width, int height, boolean hasAlpha) -{ - size_t i, numPels = width * height; - byte max = 0; - byte* pix; - - if(hasAlpha) + else { - byte* apix; - for(i = 0, pix = pixels, apix = pixels + numPels; i < numPels; ++i, pix++, apix++) + // Determine the most favorable size for the texture. + if(texQuality == TEXQ_BEST) // The best quality. { - // Only non-masked pixels count. - if(!(*apix > 0)) - continue; - - if(*pix > max) - max = *pix; + // At the best texture quality *opt, all textures are + // sized *upwards*, so no details are lost. This takes + // more memory, but naturally looks better. + *optWidth = M_CeilPow2(width); + *optHeight = M_CeilPow2(height); } - } - else - { - for(i = 0, pix = pixels; i < numPels; ++i, pix++) + else if(texQuality == 0) { - if(*pix > max) - max = *pix; + // At the lowest quality, all textures are sized down + // to the nearest power of 2. + *optWidth = M_FloorPow2(width); + *optHeight = M_FloorPow2(height); } - } - - if(!max || max == 255) - return; - - for(i = 0, pix = pixels; i < numPels; ++i, pix++) - { - *pix = (byte) MINMAX_OF(0, (float)*pix / max * 255, 255); - } -} - -static void EnhanceContrast(byte *pixels, int width, int height) -{ - int i, c; - byte* pix; - - for(i = 0, pix = pixels; i < width * height; ++i, pix += 4) - { - for(c = 0; c < 3; ++c) + else { - float f = pix[c]; - if(f < 60) // Darken dark parts. - f = (f - 70) * 1.0125f + 70; - else if(f > 185) // Lighten light parts. - f = (f - 185) * 1.0125f + 185; - - pix[c] = MINMAX_OF(0, f, 255); + // At the other quality *opts, a weighted rounding + // is used. + *optWidth = M_WeightPow2(width, 1 - texQuality / (float) TEXQ_BEST); + *optHeight = + M_WeightPow2(height, 1 - texQuality / (float) TEXQ_BEST); } } -} -static void SharpenPixels(byte* pixels, int width, int height) -{ - int x, y, c; - byte* result = M_Calloc(4 * width * height); - const float strength = .05f; - float A, B, C; + // Hardware limitations may force us to modify the preferred + // texture size. + if(*optWidth > GL_state.maxTexSize) + *optWidth = GL_state.maxTexSize; + if(*optHeight > GL_state.maxTexSize) + *optHeight = GL_state.maxTexSize; - A = strength; - B = .70710678 * strength; // 1/sqrt(2) - C = 1 + 4*A + 4*B; + // Some GL drivers seem to have problems with VERY small textures. + if(*optWidth < MINTEXWIDTH) + *optWidth = MINTEXWIDTH; + if(*optHeight < MINTEXHEIGHT) + *optHeight = MINTEXHEIGHT; - for(y = 1; y < height - 1; ++y) + if(ratioLimit) { - for(x = 1; x < width -1; ++x) + if(*optWidth > *optHeight) // Wide texture. { - byte* pix = pixels + (x + y*width) * 4; - byte* out = result + (x + y*width) * 4; - for(c = 0; c < 3; ++c) - { - int r = (C*pix[c] - A*pix[c - width] - A*pix[c + 4] - A*pix[c - 4] - - A*pix[c + width] - B*pix[c + 4 - width] - B*pix[c + 4 + width] - - B*pix[c - 4 - width] - B*pix[c - 4 + width]); - out[c] = MINMAX_OF(0, r, 255); - } - out[3] = pix[3]; + if(*optHeight < *optWidth / ratioLimit) + *optHeight = *optWidth / ratioLimit; } - } - memcpy(pixels, result, 4 * width * height); - M_Free(result); -} - -/** - * Returns the OpenGL name of the texture. - */ -DGLuint GL_PreparePatch(patchtex_t* patchTex) -{ - if(patchTex) - { - material_load_params_t params; - const gltexture_inst_t* texInst; - memset(¶ms, 0, sizeof(params)); - if(patchTex->flags & PF_MONOCHROME) - params.tex.flags |= GLTF_MONOCHROME; - if(patchTex->flags & PF_UPSCALE_AND_SHARPEN) + else // Tall texture. { - params.tex.flags |= GLTF_UPSCALE_AND_SHARPEN; - params.tex.border = 1; + if(*optWidth < *optHeight / ratioLimit) + *optWidth = *optHeight / ratioLimit; } - if((texInst = GL_PrepareGLTexture(patchTex->texId, ¶ms, NULL))) - return texInst->id; } - return 0; + + return noStretch; } /** @@ -2839,7 +2708,7 @@ gltexture_inst_t* GLTexture_Prepare(gltexture_t* tex, void* context, byte* resul if(tex->type == GLT_DETAIL) { float baMul, hiMul, loMul; - Equalize(image.pixels, image.width, image.height, &baMul, &hiMul, &loMul); + EqualizeLuma(image.pixels, image.width, image.height, &baMul, &hiMul, &loMul); if(verbose && (baMul != 1 || hiMul != 1 || loMul != 1)) { Con_Message("GLTexture_Prepare: Equalized detail texture \"%s\" (balance: %g, high amp: %g, low amp: %g).\n", @@ -2851,45 +2720,48 @@ gltexture_inst_t* GLTexture_Prepare(gltexture_t* tex, void* context, byte* resul { int scaleMethod = GL_ChooseSmartFilter(image.width, image.height, 0); int numpels = image.width * image.height; - uint8_t* rgbaPixels, *upscaledPixels; - - if(0 == (rgbaPixels = malloc(numpels * 4))) - Con_Error("GLTexture_Prepare: Failed on allocation of %lu bytes for " - "RGBA conversion buffer.", (unsigned long) (numpels * 4)); + uint8_t* newPixels; - GL_ConvertBuffer(image.width, image.height, ((image.flags & IMGF_IS_MASKED)? 2 : 1), - 4, image.pixels, rgbaPixels, 0, false); + newPixels = GL_ConvertBuffer(image.pixels, image.width, image.height, ((image.flags & IMGF_IS_MASKED)? 2 : 1), 0, false, 4); + if(newPixels != image.pixels) + { + free(image.pixels); + image.pixels = newPixels; + image.pixelSize = 4; + image.originalBits = 32; + } if(monochrome && (tex->type == GLT_DOOMPATCH || tex->type == GLT_SPRITE)) - Desaturate(rgbaPixels, image.width, image.height, 4); + Desaturate(image.pixels, image.width, image.height, image.pixelSize); - upscaledPixels = GL_SmartFilter(scaleMethod, rgbaPixels, image.width, image.height, 0, &image.width, &image.height); - if(upscaledPixels != rgbaPixels) + newPixels = GL_SmartFilter(scaleMethod, image.pixels, image.width, image.height, 0, &image.width, &image.height); + if(newPixels != image.pixels) { - free(rgbaPixels); - rgbaPixels = upscaledPixels; + free(image.pixels); + image.pixels = newPixels; } - EnhanceContrast(rgbaPixels, image.width, image.height); - //SharpenPixels(rgbaPixels, image.width, image.height); - //BlackOutlines(rgbaPixels, image.width, image.height); + EnhanceContrast(image.pixels, image.width, image.height, image.pixelSize); + //SharpenPixels(image.pixels, image.width, image.height, image.pixelSize); + //BlackOutlines(image.pixels, image.width, image.height, image.pixelSize); // Back to indexed+alpha? if(monochrome && (tex->type == GLT_DOOMPATCH || tex->type == GLT_SPRITE)) { // No. We'll convert from RGB(+A) to Luminance(+A) and upload as is. // Replace the old buffer. - free(image.pixels); - image.pixels = rgbaPixels; - image.pixelSize = 4; - GL_ConvertToLuminance(&image); - Amplify(image.pixels, image.width, image.height, image.pixelSize == 2); + AmplifyLuma(image.pixels, image.width, image.height, image.pixelSize == 2); } else { // Yes. Quantize down from RGA(+A) to Indexed(+A), replacing the old image. - GL_ConvertBuffer(image.width, image.height, 4, ((image.flags & IMGF_IS_MASKED)? 2 : 1), - rgbaPixels, image.pixels, 0, false); - free(rgbaPixels); + newPixels = GL_ConvertBuffer(image.pixels, image.width, image.height, ((image.flags & IMGF_IS_MASKED)? 2 : 1), 0, false, 4); + if(newPixels != image.pixels) + { + free(image.pixels); + image.pixels = newPixels; + image.pixelSize = ((image.flags & IMGF_IS_MASKED)? 2 : 1); + image.originalBits = image.pixelSize * 8; + } } // Lets not do this again. @@ -2897,14 +2769,14 @@ gltexture_inst_t* GLTexture_Prepare(gltexture_t* tex, void* context, byte* resul } if(fillOutlines /*&& !scaleSharp*/ && (image.flags & IMGF_IS_MASKED) && image.pixelSize == 1) - ColorOutlines(image.pixels, image.width, image.height); + ColorOutlinesIdx(image.pixels, image.width, image.height); } else { if(monochrome && tex->type == GLT_DOOMPATCH && image.pixelSize > 2) { GL_ConvertToLuminance(&image); - Amplify(image.pixels, image.width, image.height, image.pixelSize == 2); + AmplifyLuma(image.pixels, image.width, image.height, image.pixelSize == 2); } } @@ -2915,7 +2787,7 @@ gltexture_inst_t* GLTexture_Prepare(gltexture_t* tex, void* context, byte* resul { int newWidth = MIN_OF(image.width, GL_state.maxTexSize); int newHeight = MIN_OF(image.height, GL_state.maxTexSize); - uint8_t* scaledPixels = GL_ScaleBuffer32(image.pixels, image.width, image.height, image.pixelSize, + uint8_t* scaledPixels = GL_ScaleBuffer(image.pixels, image.width, image.height, image.pixelSize, newWidth, newHeight); if(scaledPixels != image.pixels) { @@ -3105,7 +2977,7 @@ if(!didDefer) { // Calculate light source properties. GL_CalcLuminance(image.pixels, image.width, image.height, image.pixelSize, 0, &texInst->data.sprite.flareX, &texInst->data.sprite.flareY, - &texInst->data.sprite.autoLightColor, &texInst->data.sprite.lumSize); + texInst->data.sprite.autoLightColor, &texInst->data.sprite.lumSize); } /** @@ -3131,13 +3003,13 @@ if(!didDefer) // Average color for glow planes and top line color. if(image.pixelSize > 1) { - averageColorRGB(texInst->data.texture.color, image.pixels, image.width, image.height); - lineAverageColorRGB(texInst->data.texture.topColor, image.pixels, image.width, image.height, 0); + FindAverageColor(image.pixels, image.width, image.height, image.pixelSize, texInst->data.texture.color); + FindAverageLineColor(image.pixels, image.width, image.height, image.pixelSize, 0, texInst->data.texture.topColor); } else { - averageColorIdx(texInst->data.texture.color, image.pixels, image.width, image.height, 0, false); - lineAverageColorIdx(texInst->data.texture.topColor, image.pixels, image.width, image.height, 0, 0, false); + FindAverageColorIdx(image.pixels, image.width, image.height, 0, false, texInst->data.texture.color); + FindAverageLineColorIdx(image.pixels, image.width, image.height, 0, 0, false, texInst->data.texture.topColor); } memcpy(texInst->data.texture.colorAmplified, texInst->data.texture.color, sizeof(texInst->data.texture.colorAmplified)); diff --git a/doomsday/engine/portable/src/image.c b/doomsday/engine/portable/src/image.c new file mode 100644 index 0000000000..1e60169905 --- /dev/null +++ b/doomsday/engine/portable/src/image.c @@ -0,0 +1,112 @@ +/**\file r_lumobjs.c + *\section License + * License: GPL + * Online License Link: http://www.gnu.org/licenses/gpl.html + * + *\author Copyright © 2003-2011 Jaakko Keränen + *\author Copyright © 2006-2011 Daniel Swanson + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "de_base.h" +#include "de_console.h" + +#include "image.h" + +void GL_ConvertToLuminance(image_t* image) +{ + assert(image); + { + int p, total = image->width * image->height; + uint8_t* alphaChannel = NULL; + uint8_t* ptr = image->pixels; + + if(image->pixelSize < 3) + { // No need to convert anything. + return; + } + + // Do we need to relocate the alpha data? + if(image->pixelSize == 4) + { // Yes. Take a copy. + alphaChannel = malloc(total); + ptr = image->pixels; + for(p = 0; p < total; ++p, ptr += image->pixelSize) + alphaChannel[p] = ptr[3]; + } + + // Average the RGB colors. + ptr = image->pixels; + for(p = 0; p < total; ++p, ptr += image->pixelSize) + { + int min = MIN_OF(ptr[0], MIN_OF(ptr[1], ptr[2])); + int max = MAX_OF(ptr[0], MAX_OF(ptr[1], ptr[2])); + image->pixels[p] = (min + max) / 2; + } + + // Do we need to relocate the alpha data? + if(alphaChannel) + { + memcpy(image->pixels + total, alphaChannel, total); + image->pixelSize = 2; + free(alphaChannel); + return; + } + + image->pixelSize = 1; + } +} + +void GL_ConvertToAlpha(image_t* image, boolean makeWhite) +{ + assert(image); + GL_ConvertToLuminance(image); + { int p, total = image->width * image->height; + for(p = 0; p < total; ++p) + { + image->pixels[total + p] = image->pixels[p]; + if(makeWhite) + image->pixels[p] = 255; + }} + image->pixelSize = 2; +} + +boolean GL_ImageHasAlpha(const image_t* img) +{ + assert(img); + if(img->pixelSize == 3) + return false; + + if(img->pixelSize == 4) + { + long i, numpels = img->width * img->height; + const uint8_t* in = img->pixels; + boolean hasAlpha = false; + + for(i = 0; i < numpels; ++i, in += 4) + if(in[3] < 255) + { + hasAlpha = true; + break; + } + return hasAlpha; + } +#if _DEBUG + Con_Error("GL_ImageHasAlpha: Attempted with non-rgb(a) image."); +#endif + return false; +} diff --git a/doomsday/engine/portable/src/m_misc.c b/doomsday/engine/portable/src/m_misc.c index 661a86c578..2e9759c408 100644 --- a/doomsday/engine/portable/src/m_misc.c +++ b/doomsday/engine/portable/src/m_misc.c @@ -1287,6 +1287,43 @@ char* M_TrimmedFloat(float val) return trimmedFloatBuffer; } +void M_ReadBits(uint numBits, const uint8_t** src, uint8_t* cb, uint8_t* out) +{ + assert(src && cb && out); + { + int offset = 0, unread = numBits; + + // Read full bytes. + if(unread >= 8) + { + do + { + out[offset++] = **src, (*src)++; + } while((unread -= 8) >= 8); + } + + if(unread != 0) + { // Read remaining bits. + uint8_t fb = 8 - unread; + + if((*cb) == 0) + (*cb) = 8; + + do + { + (*cb)--; + out[offset] <<= 1; + out[offset] |= ((**src >> (*cb)) & 0x01); + } while(--unread > 0); + + out[offset] <<= fb; + + if((*cb) == 0) + (*src)++; + } + } +} + /** * Advances time and return true if the trigger is triggered. * diff --git a/doomsday/engine/portable/src/r_lumobjs.c b/doomsday/engine/portable/src/r_lumobjs.c index c827a242f6..a71e92a2de 100644 --- a/doomsday/engine/portable/src/r_lumobjs.c +++ b/doomsday/engine/portable/src/r_lumobjs.c @@ -380,7 +380,7 @@ void LO_AddLuminous(mobj_t* mo) spriteframe_t* sprFrame; spritetex_t* sprTex; material_t* mat; - rgbcol_t autoLightColor; + float autoLightColor[3]; material_snapshot_t ms; const gltexture_inst_t* texInst; diff --git a/doomsday/engine/portable/src/rend_particle.c b/doomsday/engine/portable/src/rend_particle.c index d716e0e076..7ab6ee2677 100644 --- a/doomsday/engine/portable/src/rend_particle.c +++ b/doomsday/engine/portable/src/rend_particle.c @@ -123,7 +123,7 @@ static byte loadParticleTexture(uint particleTex, boolean silent) { assert(particleTex < MAX_PTC_TEXTURES); { - ddstring_t foundPath, searchPath, suffix = { "-ck" }; + ddstring_t foundPath, searchPath, suffix = { "-ck" }; image_t image; byte result = 0;