sl@0: /* sl@0: * Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies). sl@0: * All rights reserved. sl@0: * This component and the accompanying materials are made available sl@0: * under the terms of the License "Eclipse Public License v1.0" sl@0: * which accompanies this distribution, and is available sl@0: * at the URL "http://www.eclipse.org/legal/epl-v10.html". sl@0: * sl@0: * Initial Contributors: sl@0: * Nokia Corporation - initial contribution. sl@0: * sl@0: * Contributors: sl@0: * sl@0: * Description: Image implementation sl@0: * sl@0: */ sl@0: sl@0: sl@0: /*! sl@0: * \internal sl@0: * \file sl@0: * \brief Image implementation sl@0: * sl@0: */ sl@0: sl@0: #ifndef M3G_CORE_INCLUDE sl@0: # error included by m3g_core.c; do not compile separately. sl@0: #endif sl@0: sl@0: #include "m3g_image.h" sl@0: #include "m3g_texture.h" sl@0: sl@0: /* Declare prototypes for some of the helper functions called from the sl@0: * platform-dependent code included below */ sl@0: sl@0: static M3Gint m3gBytesPerPixel(M3GPixelFormat format); sl@0: static M3Gint m3gGetNumMipmapLevels(M3Gint w, M3Gint h); sl@0: static void m3gDownsample(M3GPixelFormat format, const M3Gubyte *srcPixels, M3Gint *pw, M3Gint *ph, M3Gubyte *dstPixels); sl@0: static void m3gFreeImageData(Image *img); sl@0: sl@0: /* Include platform-dependent functionality */ sl@0: #include "m3g_image.inl" sl@0: sl@0: /* Size of the buffer used in pixel format conversions; this should be sl@0: * an even number */ sl@0: #define SPAN_BUFFER_SIZE 32 sl@0: sl@0: M3G_CT_ASSERT((SPAN_BUFFER_SIZE & 1) == 0); sl@0: sl@0: /*---------------------------------------------------------------------- sl@0: * Private functions sl@0: *--------------------------------------------------------------------*/ sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Destroys this Image object. sl@0: * sl@0: * \param obj Image object sl@0: */ sl@0: static void m3gDestroyImage(Object *obj) sl@0: { sl@0: Image *image = (Image*)obj; sl@0: Interface *m3g = M3G_INTERFACE(image); sl@0: M3G_VALIDATE_OBJECT(image); sl@0: sl@0: if (!image->copyOf) { sl@0: m3gFreeObject(m3g, image->data); sl@0: m3gFreeObject(m3g, image->mipData); sl@0: } sl@0: M3G_ASSIGN_REF(image->copyOf, NULL); sl@0: sl@0: if (image->powerOfTwo != image) { sl@0: M3G_ASSIGN_REF(image->powerOfTwo, NULL); sl@0: } sl@0: sl@0: # if !defined(M3G_NGL_TEXTURE_API) sl@0: if (image->texObject) { sl@0: m3gDeleteGLTextures(m3g, 1, &image->texObject); sl@0: } sl@0: if (image->large != NULL) { sl@0: m3gDestroyLargeImage(image); sl@0: } sl@0: # endif sl@0: sl@0: m3gDestroyObject(obj); sl@0: } sl@0: sl@0: /*--------------------------------------------------------------------*/ sl@0: sl@0: #define RED(argb) (((argb) >> 16) & 0xFFu) sl@0: #define GREEN(argb) (((argb) >> 8) & 0xFFu) sl@0: #define BLUE(argb) ( (argb) & 0xFFu) sl@0: #define ALPHA(argb) (((argb) >> 24) & 0xFFu) sl@0: sl@0: #define RGBSUM(argb) (0x4CB2u * RED(argb) + \ sl@0: 0x9691u * GREEN(argb) + \ sl@0: 0x1D3Eu * BLUE(argb)) sl@0: sl@0: /*! sl@0: * \internal \brief ARGB -> A sl@0: */ sl@0: static void convertARGBToA8(const M3Guint *src, M3Gsizei count, M3Gubyte *dst) sl@0: { sl@0: while (count--) { sl@0: *dst++ = (M3Gubyte) ALPHA(*src++); sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal \brief ARGB -> L sl@0: */ sl@0: static void convertARGBToL8(const M3Guint *src, M3Gsizei count, M3Gubyte *dst) sl@0: { sl@0: while (count--) { sl@0: M3Guint argb = *src++; sl@0: M3Guint sum = RGBSUM(argb); sl@0: *dst++ = (M3Gubyte)(sum >> 16); sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal \brief ARGB -> LA */ sl@0: static void convertARGBToLA4(const M3Guint *src, M3Gsizei count, M3Gubyte *dst) sl@0: { sl@0: while (count--) { sl@0: M3Guint argb = *src++; sl@0: M3Guint sum = RGBSUM(argb); sl@0: *dst++ = (M3Gubyte)(((sum >> 16) & 0xF0) | ((argb >> 28) & 0x0F)); sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal \brief ARGB -> LA8 sl@0: */ sl@0: static void convertARGBToLA8(const M3Guint *src, M3Gsizei count, M3Gubyte *dst) sl@0: { sl@0: while (count--) { sl@0: M3Guint argb = *src++; sl@0: M3Guint sum = RGBSUM(argb); sl@0: *dst++ = (M3Gubyte)(sum >> 16); /* L */ sl@0: *dst++ = (M3Gubyte) ALPHA(argb); sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal \brief ARGB -> RGB sl@0: */ sl@0: static void convertARGBToRGB8(const M3Guint *src, M3Gsizei count, M3Gubyte *dst) sl@0: { sl@0: while (count--) { sl@0: M3Guint argb = *src++; sl@0: *dst++ = (M3Gubyte) RED(argb); sl@0: *dst++ = (M3Gubyte) GREEN(argb); sl@0: *dst++ = (M3Gubyte) BLUE(argb); sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal \brief ARGB -> RGB565 sl@0: */ sl@0: static void convertARGBToRGB565(const M3Guint *src, M3Gsizei count, M3Gubyte *dst) sl@0: { sl@0: while (count--) { sl@0: M3Guint argb = *src++; sl@0: *(M3Gushort*)dst = (M3Gushort)(((argb >> 8) & 0xF800u)| sl@0: ((argb >> 5) & 0x07E0u)| sl@0: ((argb >> 3) & 0x001Fu)); sl@0: dst += 2; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal \brief ARGB -> RGBA sl@0: */ sl@0: static void convertARGBToRGBA8(const M3Guint *src, M3Gsizei count, M3Gubyte *dst) sl@0: { sl@0: while (count--) { sl@0: M3Guint argb = *src++; sl@0: *dst++ = (M3Gubyte) RED(argb); sl@0: *dst++ = (M3Gubyte) GREEN(argb); sl@0: *dst++ = (M3Gubyte) BLUE(argb); sl@0: *dst++ = (M3Gubyte) ALPHA(argb); sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal \brief ARGB -> BGRA sl@0: */ sl@0: static void convertARGBToBGRA8(const M3Guint *src, M3Gsizei count, M3Gubyte *dst) sl@0: { sl@0: while (count--) { sl@0: M3Guint argb = *src++; sl@0: *dst++ = (M3Gubyte) BLUE(argb); sl@0: *dst++ = (M3Gubyte) GREEN(argb); sl@0: *dst++ = (M3Gubyte) RED(argb); sl@0: *dst++ = (M3Gubyte) ALPHA(argb); sl@0: } sl@0: } sl@0: sl@0: #undef RED sl@0: #undef GREEN sl@0: #undef BLUE sl@0: #undef ALPHA sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Converts a span of ARGB pixels to another format sl@0: * sl@0: * \param src source pixels sl@0: * \param count pixel count sl@0: * \param dstFormat destination format sl@0: * \param dst destination pixels sl@0: */ sl@0: static void convertFromARGB(const M3Guint *src, sl@0: M3Gsizei count, sl@0: M3GPixelFormat dstFormat, sl@0: M3Gubyte *dst) sl@0: { sl@0: switch (dstFormat) { sl@0: case M3G_L8: sl@0: convertARGBToL8(src, count, dst); sl@0: break; sl@0: case M3G_A8: sl@0: convertARGBToA8(src, count, dst); sl@0: break; sl@0: case M3G_LA4: sl@0: convertARGBToLA4(src, count, dst); sl@0: break; sl@0: case M3G_LA8: sl@0: convertARGBToLA8(src, count, dst); sl@0: break; sl@0: case M3G_RGB8: sl@0: convertARGBToRGB8(src, count, dst); sl@0: break; sl@0: case M3G_RGB565: sl@0: convertARGBToRGB565(src, count, dst); sl@0: break; sl@0: case M3G_RGBA8: sl@0: case M3G_RGB8_32: sl@0: convertARGBToRGBA8(src, count, dst); sl@0: break; sl@0: case M3G_BGRA8: sl@0: case M3G_BGR8_32: sl@0: convertARGBToBGRA8(src, count, dst); sl@0: break; sl@0: default: sl@0: M3G_ASSERT(M3G_FALSE); /* conversion not supported */ sl@0: } sl@0: } sl@0: sl@0: /*--------------------------------------------------------------------*/ sl@0: sl@0: /*! sl@0: * \internal \brief A8 -> ARGB sl@0: */ sl@0: static void convertA8ToARGB(const M3Gubyte *src, M3Gsizei count, M3Guint *dst) sl@0: { sl@0: while (count--) { sl@0: M3Guint argb = M3G_RGB_MASK; sl@0: argb |= ((M3Guint) *src++) << 24; sl@0: *dst++ = argb; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal \brief L8 -> ARGB sl@0: */ sl@0: static void convertL8ToARGB(const M3Gubyte *src, M3Gsizei count, M3Guint *dst) sl@0: { sl@0: while (count--) { sl@0: M3Guint argb = *src++; sl@0: argb |= (argb << 8) | (argb << 16); sl@0: argb |= M3G_ALPHA_MASK; sl@0: *dst++ = argb; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal \brief LA8 -> ARGB sl@0: */ sl@0: static void convertLA8ToARGB(const M3Gubyte *src, M3Gsizei count, M3Guint *dst) sl@0: { sl@0: while (count--) { sl@0: M3Guint argb = *src++; sl@0: argb |= (argb << 8) | (argb << 16); sl@0: argb |= ((M3Guint) *src++) << 24; sl@0: *dst++ = argb; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal \brief RGB8 -> ARGB sl@0: */ sl@0: static void convertRGB8ToARGB(const M3Gubyte *src, M3Gsizei count, M3Guint *dst) sl@0: { sl@0: while (count--) { sl@0: M3Guint argb = M3G_ALPHA_MASK; sl@0: argb |= ((M3Guint)(*src++)) << 16; sl@0: argb |= ((M3Guint)(*src++)) << 8; sl@0: argb |= (M3Guint)(*src++); sl@0: *dst++ = argb; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal \brief RGB565 -> ARGB sl@0: */ sl@0: static void convertRGB565ToARGB(const M3Gubyte *src, M3Gsizei count, M3Guint *dst) sl@0: { sl@0: while (count--) { sl@0: M3Guint argb = M3G_ALPHA_MASK; sl@0: const M3Guint rgb565 = *(const M3Gushort*)src; sl@0: argb |= ((rgb565 & 0xF800u) << 8)|((rgb565 & 0xE000u) << 3); sl@0: argb |= ((rgb565 & 0x07E0u) << 5)|((rgb565 & 0x0600u) >> 1); sl@0: argb |= ((rgb565 & 0x001Fu) << 3)|((rgb565 & 0x001Cu) >> 2); sl@0: *dst++ = argb; sl@0: src += 2; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal \brief RGBA8 -> ARGB sl@0: */ sl@0: static void convertRGBA8ToARGB(const M3Gubyte *src, M3Gsizei count, M3Guint *dst) sl@0: { sl@0: while (count--) { sl@0: M3Guint argb; sl@0: argb = ((M3Guint)(*src++)) << 16; sl@0: argb |= ((M3Guint)(*src++)) << 8; sl@0: argb |= (M3Guint)(*src++); sl@0: argb |= ((M3Guint)(*src++)) << 24; sl@0: *dst++ = argb; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal \brief BGRA8 -> ARGB sl@0: */ sl@0: static void convertBGRA8ToARGB(const M3Gubyte *src, M3Gsizei count, M3Guint *dst) sl@0: { sl@0: while (count--) { sl@0: M3Guint argb; sl@0: argb = (M3Guint)(*src++); sl@0: argb |= ((M3Guint)(*src++)) << 8; sl@0: argb |= ((M3Guint)(*src++)) << 16; sl@0: argb |= ((M3Guint)(*src++)) << 24; sl@0: *dst++ = argb; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Converts a span of pixels to ARGB sl@0: * sl@0: * \param srcFormat source format sl@0: * \param src source pixels sl@0: * \param count pixel count sl@0: * \param dst destination pixels sl@0: */ sl@0: static void convertToARGB(M3GPixelFormat srcFormat, sl@0: const M3Gubyte *src, sl@0: M3Gsizei count, sl@0: M3Guint *dst) sl@0: { sl@0: switch (srcFormat) { sl@0: case M3G_A8: sl@0: convertA8ToARGB(src, count, dst); sl@0: break; sl@0: case M3G_L8: sl@0: convertL8ToARGB(src, count, dst); sl@0: break; sl@0: case M3G_LA8: sl@0: convertLA8ToARGB(src, count, dst); sl@0: break; sl@0: case M3G_RGB8: sl@0: convertRGB8ToARGB(src, count, dst); sl@0: break; sl@0: case M3G_RGB565: sl@0: convertRGB565ToARGB(src, count, dst); sl@0: break; sl@0: case M3G_RGBA8: sl@0: case M3G_RGB8_32: sl@0: convertRGBA8ToARGB(src, count, dst); sl@0: break; sl@0: case M3G_BGRA8: sl@0: case M3G_BGR8_32: sl@0: convertBGRA8ToARGB(src, count, dst); sl@0: break; sl@0: default: sl@0: M3G_ASSERT(M3G_FALSE); /* conversion not supported */ sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Fast path for BGRA-to-RGBA conversion sl@0: */ sl@0: #if defined (M3G_HW_ARMV6) sl@0: __asm void fastConvertBGRAToRGBA(const M3Gubyte *src, M3Gsizei srcStride, sl@0: M3Gsizei width, M3Gsizei height, sl@0: M3Gubyte *dst) sl@0: { sl@0: // r0 = *src sl@0: // r1 = srcStride sl@0: // r2 = width sl@0: // r3 = height sl@0: // sp[0] = *dst sl@0: sl@0: CODE32 sl@0: sl@0: CMP r3, #0 // if height = 0, do nothing sl@0: BXEQ lr sl@0: sl@0: STMFD sp!, {r4-r12, lr} sl@0: sl@0: LDR r12, [sp, #(10*4)] sl@0: SUB r1, r1, r2, LSL #2 sl@0: MOV r14, r2 sl@0: sl@0: _fastConvertBGRAToRGBA_outerLoop sl@0: MOVS r2, r14, ASR #2 // amount of 4x32 bit writes sl@0: BEQ _fastConvertBGRAToRGBA_tail sl@0: sl@0: _fastConvertBGRAToRGBA_innerLoop sl@0: sl@0: LDMIA r0!, {r4-r7} // AARRGGBB sl@0: SUBS r2, #1 sl@0: REV r4, r4 // BBGGRRAA sl@0: REV r5, r5 sl@0: REV r6, r6 sl@0: REV r7, r7 sl@0: MOV r8, r4, ROR #8 // AABBGGRR sl@0: MOV r9, r5, ROR #8 sl@0: MOV r10, r6, ROR #8 sl@0: MOV r11, r7, ROR #8 sl@0: STMIA r12!, {r8-r11} sl@0: BNE _fastConvertBGRAToRGBA_innerLoop sl@0: sl@0: _fastConvertBGRAToRGBA_tail sl@0: MOV r2, r14, ASR #2 sl@0: SUBS r2, r14, r2, LSL #2 // number of remaining writes in the tail sl@0: sl@0: _fastConvertBGRAToRGBA_tail_loop sl@0: sl@0: LDRNE r4, [r0], #4 sl@0: REVNE r4, r4 sl@0: MOVNE r8, r4, ROR #8 sl@0: STRNE r8, [r12], #4 sl@0: SUBNES r2, #1 sl@0: BNE _fastConvertBGRAToRGBA_tail_loop sl@0: sl@0: SUBS r3, #1 sl@0: ADD r0, r0, r1 sl@0: BNE _fastConvertBGRAToRGBA_outerLoop sl@0: sl@0: LDMFD sp!, {r4-r12, lr} sl@0: BX lr sl@0: sl@0: } sl@0: #else /* #if defined (M3G_HW_ARMV6) */ sl@0: static void fastConvertBGRAToRGBA(const M3Gubyte *src, M3Gsizei srcStride, sl@0: M3Gsizei width, M3Gsizei height, sl@0: M3Gubyte *dst) sl@0: { sl@0: unsigned int pixel, pixel2; sl@0: unsigned int temp; sl@0: unsigned int mask = 0x00ff00ff; sl@0: int spanwidth = (width >> 1) - 1; sl@0: int x, y; sl@0: unsigned int *d = (unsigned int *)dst; sl@0: sl@0: M3G_ASSERT(width > 2); sl@0: sl@0: for (y = 0; y < height; ++y) { sl@0: unsigned int *s = (unsigned int *)(src + y*srcStride); sl@0: sl@0: pixel = *s++; sl@0: sl@0: for (x = 0; x < spanwidth; ++x) { sl@0: pixel2 = *s++; sl@0: sl@0: temp = pixel & mask; /* 00RR00BB */ sl@0: pixel = pixel - temp; /* AA00GG00 */ sl@0: pixel = pixel | (temp << 16); /* AABBGG00 */ sl@0: *d++ = pixel | (temp >> 16); /* AABBGGRR */ sl@0: sl@0: pixel = *s++; sl@0: sl@0: temp = pixel2 & mask; /* 00RR00BB */ sl@0: pixel2 = pixel2 - temp; /* AA00GG00 */ sl@0: pixel2 = pixel2 | (temp << 16); /* AABBGG00 */ sl@0: *d++ = pixel2 | (temp >> 16); /* AABBGGRR */ sl@0: } sl@0: sl@0: pixel2 = *s++; sl@0: temp = pixel & mask; /* 00RR00BB */ sl@0: pixel = pixel - temp; /* AA00GG00 */ sl@0: pixel = pixel | (temp << 16); /* AABBGG00 */ sl@0: *d++ = pixel | (temp >> 16); /* AABBGGRR */ sl@0: sl@0: temp = pixel2 & mask; /* 00RR00BB */ sl@0: pixel2 = pixel2 - temp; /* AA00GG00 */ sl@0: pixel2 = pixel2 | (temp << 16); /* AABBGG00 */ sl@0: *d++ = pixel2 | (temp >> 16); /* AABBGGRR */ sl@0: } sl@0: } sl@0: #endif /* #if defined (M3G_HW_ARMV6) */ sl@0: sl@0: /*--------------------------------------------------------------------*/ sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Maps a logical image format to an internal pixel format sl@0: * sl@0: * \param imgFormat logical image format sl@0: * \param paletted paletted flag sl@0: * \return the internal image pixel format sl@0: */ sl@0: static M3GPixelFormat getInternalFormat(M3GImageFormat imgFormat, sl@0: M3Gbool paletted) sl@0: { sl@0: if (paletted) { sl@0: switch (imgFormat) { sl@0: case M3G_RGB: sl@0: # if defined(M3G_NGL_TEXTURE_API) sl@0: return M3G_PALETTE8_RGB8_32; sl@0: # else sl@0: return M3G_PALETTE8_RGB8; sl@0: # endif sl@0: case M3G_RGBA: sl@0: return M3G_PALETTE8_RGBA8; sl@0: default: sl@0: M3G_ASSERT(M3G_FALSE); sl@0: return (M3GPixelFormat)0; sl@0: } sl@0: } sl@0: else { sl@0: M3GPixelFormat format = m3gPixelFormat(imgFormat); sl@0: sl@0: # if defined(M3G_NGL_TEXTURE_API) sl@0: if (format == M3G_RGB8) { sl@0: return (M3G_USE_16BIT_TEXTURES) ? M3G_RGB565 : M3G_RGB8_32; sl@0: } sl@0: if (format == M3G_LA8) { sl@0: return M3G_LA4; sl@0: } sl@0: # endif sl@0: sl@0: return format; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Gets the correct pixel format for setting data to an image sl@0: */ sl@0: static M3GPixelFormat m3gInputDataFormat(const Image *img) sl@0: { sl@0: /* Any of the paletted formats will do for a paletted image, as sl@0: * they all have 8-bit indices; we pick PALETTE8_RGBA8 here */ sl@0: sl@0: if (img->flags & M3G_PALETTED) { sl@0: return M3G_PALETTE8_RGBA8; sl@0: } sl@0: sl@0: return m3gPixelFormat(img->format); sl@0: } sl@0: sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Returns log2(resolution)+1. Assumes that resolution is power of two. sl@0: * sl@0: * \param w width in pixels sl@0: * \param h height in pixels sl@0: * \return number of needed mipmap levels sl@0: */ sl@0: static M3Gint m3gGetNumMipmapLevels(M3Gint w, M3Gint h) sl@0: { sl@0: M3Gint res = (w > h) ? w : h; sl@0: M3Gint levels = 0; sl@0: while (res > 0) { sl@0: ++levels; sl@0: res >>= 1; sl@0: }; sl@0: return levels; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Downsamples an image to half the original size sl@0: * sl@0: * sl@0: * \param format pixel format sl@0: * \param srcPixels source pixels sl@0: * \param pw pointer to width sl@0: * \param ph pointer to height sl@0: * \param dstPixels destination pixels sl@0: */ sl@0: static void m3gDownsample(M3GPixelFormat format, sl@0: const M3Gubyte *srcPixels, sl@0: M3Gint *pw, M3Gint *ph, sl@0: M3Gubyte *dstPixels) sl@0: { sl@0: M3Gint i, j, bpp, pixStride, lineStride; sl@0: M3Gint w = *pw, h = *ph; sl@0: M3Gubyte *dst; sl@0: M3Guint temp[2][SPAN_BUFFER_SIZE/2]; sl@0: sl@0: M3G_ASSERT_PTR(srcPixels); sl@0: M3G_ASSERT(w >= 1 && h >= 1); sl@0: sl@0: bpp = m3gBytesPerPixel(format); sl@0: lineStride = (h > 1) ? w * bpp : 0; sl@0: pixStride = (w > 1) ? bpp : 0; sl@0: sl@0: dst = dstPixels; sl@0: sl@0: /* Iterate over buffer-sized blocks in the image */ sl@0: sl@0: for (j = 0; j < h; j += 2) { sl@0: for (i = 0; i < w; i += SPAN_BUFFER_SIZE/2) { sl@0: sl@0: /* Fill the buffer from the source image */ sl@0: sl@0: const M3Gubyte *src = srcPixels + (j*lineStride + i*pixStride); sl@0: M3Gint c = SPAN_BUFFER_SIZE/2; sl@0: if (w - i < c) { sl@0: c = w - i; sl@0: } sl@0: convertToARGB(format, src, c, &temp[0][0]); sl@0: convertToARGB(format, src + lineStride, c, &temp[1][0]); sl@0: if (w == 1) { sl@0: temp[0][1] = temp[0][0]; sl@0: temp[1][1] = temp[1][0]; sl@0: } sl@0: sl@0: /* Average the pixels in the buffer */ sl@0: { sl@0: # define AG_MASK 0xFF00FF00u sl@0: # define RB_MASK 0x00FF00FFu sl@0: sl@0: M3Gint k; sl@0: for (k = 0; k < c; k += 2) { sl@0: M3Guint ag, rb; sl@0: sl@0: /* Add two components in parallel */ sl@0: sl@0: ag = ((temp[0][k] & AG_MASK) >> 8) sl@0: + ((temp[1][k] & AG_MASK) >> 8) sl@0: + ((temp[0][k+1] & AG_MASK) >> 8) sl@0: + ((temp[1][k+1] & AG_MASK) >> 8); sl@0: sl@0: rb = (temp[0][k] & RB_MASK) sl@0: + (temp[1][k] & RB_MASK) sl@0: + (temp[0][k+1] & RB_MASK) sl@0: + (temp[1][k+1] & RB_MASK); sl@0: sl@0: /* Shift to divide by 4, adding ½ for rounding */ sl@0: sl@0: temp[0][k>>1] = ((((ag + 0x00020002u) << 6) & AG_MASK) | sl@0: (((rb + 0x00020002u) >> 2) & RB_MASK)); sl@0: } sl@0: sl@0: # undef AG_MASK sl@0: # undef RB_MASK sl@0: } sl@0: sl@0: /* Write result to the output buffer */ sl@0: sl@0: convertFromARGB(&temp[0][0], c>>1, format, dst); sl@0: dst += (c>>1) * bpp; sl@0: } sl@0: } sl@0: sl@0: /* Return output width and height */ sl@0: sl@0: if (w > 1) { sl@0: *pw = (w >> 1); sl@0: } sl@0: if (h > 1) { sl@0: *ph = (h >> 1); sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Returns the OpenGL minification filter corresponding to M3G sl@0: * filtering flags sl@0: */ sl@0: static GLenum m3gGetGLMinFilter(M3Genum levelFilter, M3Genum imageFilter) sl@0: { sl@0: static const GLenum minFilter[3][2] = { sl@0: GL_LINEAR, GL_NEAREST, sl@0: GL_LINEAR_MIPMAP_LINEAR, GL_NEAREST_MIPMAP_LINEAR, sl@0: GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_NEAREST sl@0: }; sl@0: sl@0: return minFilter[levelFilter - M3G_FILTER_BASE_LEVEL][imageFilter - M3G_FILTER_LINEAR]; sl@0: } sl@0: sl@0: /*---------------------------------------------------------------------- sl@0: * Internal functions sl@0: *--------------------------------------------------------------------*/ sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Converts an internal ARGB color to four GLfixed components sl@0: */ sl@0: static void m3gGLColor(M3Guint argb, GLfixed *dst) sl@0: { sl@0: GLfixed r, g, b, a; sl@0: sl@0: r = (GLfixed)((argb & 0x00FF0000u) >> 16); sl@0: g = (GLfixed)((argb & 0x0000FF00u) >> 8); sl@0: b = (GLfixed)( argb & 0x000000FFu ); sl@0: a = (GLfixed)((argb & 0xFF000000u) >> 24); sl@0: sl@0: dst[0] = ((r << 8) | r) + (r >> 7); sl@0: dst[1] = ((g << 8) | g) + (g >> 7); sl@0: dst[2] = ((b << 8) | b) + (b >> 7); sl@0: dst[3] = ((a << 8) | a) + (a >> 7); sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Binds an image into the current texture unit and sets up sl@0: * texture filtering sl@0: */ sl@0: static void m3gBindTextureImage(Image *img, M3Genum levelFilter, M3Genum imageFilter) sl@0: { sl@0: M3G_ASSERT_GL; sl@0: sl@0: /* We have no mipmap generation for paletted images, so disable sl@0: * mipmapping in that case */ sl@0: sl@0: if (m3gIsInternallyPaletted(img)) { sl@0: levelFilter = M3G_FILTER_BASE_LEVEL; sl@0: } sl@0: sl@0: /* Bind the OpenGL texture object, generating mipmaps if sl@0: * required */ sl@0: sl@0: m3gBindTextureObject(img, levelFilter != M3G_FILTER_BASE_LEVEL); sl@0: sl@0: /* Set up OpenGL texture filtering according to our flags */ sl@0: sl@0: glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, sl@0: (imageFilter == M3G_FILTER_LINEAR) ? GL_LINEAR : GL_NEAREST); sl@0: glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, sl@0: m3gGetGLMinFilter(levelFilter, imageFilter)); sl@0: sl@0: M3G_ASSERT_GL; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Maps a logical image format to the matching default pixel sl@0: * format sl@0: * sl@0: * \param imgFormat logical image format sl@0: * \return a one-byte-per-pixel pixel format sl@0: */ sl@0: static M3GPixelFormat m3gPixelFormat(M3GImageFormat imgFormat) sl@0: { sl@0: switch (imgFormat) { sl@0: case M3G_ALPHA: sl@0: return M3G_A8; sl@0: case M3G_LUMINANCE: sl@0: return M3G_L8; sl@0: case M3G_LUMINANCE_ALPHA: sl@0: return M3G_LA8; sl@0: case M3G_RGB: sl@0: return M3G_RGB8; sl@0: case M3G_RGBA: sl@0: return M3G_RGBA8; sl@0: default: sl@0: M3G_ASSERT(M3G_FALSE); sl@0: return M3G_NO_FORMAT; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Returns the number of bytes per pixel in a given pixel format sl@0: * sl@0: * \param format pixel format sl@0: * \return bytes per pixel sl@0: */ sl@0: static M3Gint m3gBytesPerPixel(M3GPixelFormat format) sl@0: { sl@0: switch (format) { sl@0: case M3G_L8: sl@0: case M3G_A8: sl@0: case M3G_LA4: sl@0: case M3G_PALETTE8_RGB8: sl@0: case M3G_PALETTE8_RGB8_32: sl@0: case M3G_PALETTE8_RGBA8: sl@0: return 1; sl@0: case M3G_RGB4: sl@0: case M3G_RGB565: sl@0: case M3G_RGBA4: sl@0: case M3G_RGB5A1: sl@0: case M3G_LA8: sl@0: return 2; sl@0: case M3G_RGB8: sl@0: return 3; sl@0: case M3G_RGBA8: sl@0: case M3G_BGRA8: sl@0: case M3G_ARGB8: sl@0: case M3G_BGR8_32: sl@0: case M3G_RGB8_32: sl@0: return 4; sl@0: default: sl@0: M3G_ASSERT(M3G_FALSE); sl@0: return 0; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Converts pixels between formats sl@0: * sl@0: * \note Only a limited subset of source and destination formats may sl@0: * be supported; see the \c convert functions in m3g_image.c sl@0: * sl@0: * \param srcFormat source format sl@0: * \param src source pixels sl@0: * \param dstFormat destination format sl@0: * \param dst destination pixels sl@0: * \param count pixel count sl@0: */ sl@0: static void m3gConvertPixels(M3GPixelFormat srcFormat, const M3Gubyte *src, sl@0: M3GPixelFormat dstFormat, M3Gubyte *dst, sl@0: M3Gsizei count) sl@0: { sl@0: M3Guint temp[SPAN_BUFFER_SIZE]; sl@0: const char endianTest[4] = { 1, 0, 0, 0 }; sl@0: sl@0: M3Guint srcBpp = m3gBytesPerPixel(srcFormat); sl@0: M3Guint dstBpp = m3gBytesPerPixel(dstFormat); sl@0: M3G_ASSERT(srcBpp > 0 && dstBpp > 0); sl@0: sl@0: while (count > 0) { sl@0: M3Gsizei n = count; sl@0: sl@0: /* Check the source and destination formats to avoid sl@0: the intermediate ARGB format conversion. */ sl@0: if (((srcFormat == M3G_RGBA8 && (dstFormat == M3G_BGRA8 || dstFormat == M3G_BGR8_32)) sl@0: || (dstFormat == M3G_RGBA8 && (srcFormat == M3G_BGRA8 || srcFormat == M3G_BGR8_32))) sl@0: && (n > 2) && ((*(const int *)endianTest) == 1)) { sl@0: /* use fast path for RGBA<->BGRA conversion */ sl@0: fastConvertBGRAToRGBA(src, n * srcBpp, n, 1, dst); sl@0: } else if (srcFormat == M3G_ARGB8 && dstFormat != M3G_ARGB8) { sl@0: convertFromARGB((M3Guint*)src, n, dstFormat, dst); sl@0: } else if (srcFormat != M3G_ARGB8 && dstFormat == M3G_ARGB8) { sl@0: convertToARGB(srcFormat, src, n, (M3Guint*)dst); sl@0: } else { sl@0: /* no luck, do the conversion via ARGB (source format -> ARGB -> destination format) */ sl@0: n = (count < SPAN_BUFFER_SIZE) ? count : SPAN_BUFFER_SIZE; sl@0: convertToARGB(srcFormat, src, n, temp); sl@0: convertFromARGB(temp, n, dstFormat, dst); sl@0: } sl@0: count -= n; sl@0: src += n * srcBpp; sl@0: dst += n * dstBpp; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Copies image data. The source image is copied to sl@0: * the destination image. sl@0: * sl@0: * \param dst destination image sl@0: * \param src source image sl@0: */ sl@0: static void m3gCopyImagePixels(Image *dst, sl@0: const Image *src) sl@0: { sl@0: const M3Gubyte *pSrc; sl@0: M3Gubyte *pDst; sl@0: M3Gint bpp; sl@0: sl@0: /* Check inputs (debug only!) */ sl@0: M3G_VALIDATE_OBJECT(dst); sl@0: M3G_VALIDATE_OBJECT(src); sl@0: sl@0: M3G_ASSERT(src->internalFormat == dst->internalFormat); sl@0: M3G_ASSERT(src->format == dst->format); sl@0: sl@0: M3G_ASSERT(src->paletteBytes == dst->paletteBytes); sl@0: sl@0: /* Compute source and destination pixel data pointers */ sl@0: pSrc = (M3Gubyte *)m3gMapObject(M3G_INTERFACE(src), src->data); sl@0: pDst = (M3Gubyte *)m3gMapObject(M3G_INTERFACE(dst), dst->data); sl@0: sl@0: bpp = m3gBytesPerPixel(src->internalFormat); sl@0: sl@0: if (src->paletteBytes > 0) { sl@0: m3gCopy(pDst, pSrc, src->paletteBytes); sl@0: pDst += dst->paletteBytes; sl@0: pSrc += src->paletteBytes; sl@0: } sl@0: sl@0: /* Do a straight copy if the sizes match, or resample if not */ sl@0: if (src->width == dst->width && src->height == dst->height ) { sl@0: m3gCopy(pDst, pSrc, src->width * src->height * bpp); sl@0: } sl@0: else { sl@0: /* Adder values as 8.8 fixed point */ sl@0: M3Gint xAdd, yAdd; sl@0: M3Gint x, y; sl@0: sl@0: xAdd = (256 * src->width) / dst->width; sl@0: yAdd = (256 * src->height) / dst->height; sl@0: sl@0: for (y = 0; y < dst->height; y++) { sl@0: for (x = 0; x < dst->width; x++) { sl@0: m3gCopy(pDst, pSrc + bpp * (((xAdd * x) >> 8) + ((yAdd * y) >> 8) * src->width), bpp); sl@0: pDst += bpp; sl@0: } sl@0: } sl@0: } sl@0: sl@0: m3gUnmapObject(M3G_INTERFACE(dst), dst->data); sl@0: m3gUnmapObject(M3G_INTERFACE(src), src->data); sl@0: sl@0: m3gInvalidateImage(dst); sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Invalidates any cached data for this image sl@0: * sl@0: * Used when rendering to the image. sl@0: * sl@0: * \param img Image object sl@0: */ sl@0: static void m3gInvalidateImage(Image *img) sl@0: { sl@0: M3G_VALIDATE_OBJECT(img); sl@0: img->dirty = M3G_TRUE; sl@0: sl@0: # if !defined(M3G_NGL_TEXTURE_API) sl@0: if (img->large) { sl@0: img->large->dirty = M3G_TRUE; sl@0: } sl@0: # endif /*M3G_NGL_TEXTURE_API*/ sl@0: sl@0: if (img->powerOfTwo != img) { sl@0: img->powerOfTwoDirty = M3G_TRUE; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Object3D method. sl@0: * sl@0: * \param originalObj original Image object sl@0: * \param cloneObj pointer to cloned Image object sl@0: * \param pairs array for all object-duplicate pairs sl@0: * \param numPairs number of pairs sl@0: */ sl@0: static M3Gbool m3gImageDuplicate(const Object *originalObj, sl@0: Object **cloneObj, sl@0: Object **pairs, sl@0: M3Gint *numPairs) sl@0: { sl@0: Image *original = (Image *)originalObj; sl@0: Image *clone; sl@0: sl@0: /* If the original image still has its pixel data, make a full sl@0: * copy -- this is wasteful for immutable images, but the shame's sl@0: * on the user in that case */ sl@0: sl@0: if (original->data) { sl@0: clone = (Image*) m3gCreateImage(originalObj->interface, sl@0: original->format, sl@0: original->width, sl@0: original->height, sl@0: original->flags); sl@0: } sl@0: else { sl@0: sl@0: /* Otherwise, just point to the original and use its data sl@0: * buffers */ sl@0: sl@0: clone = (Image*) m3gAlloc(M3G_INTERFACE(original), sizeof(*clone)); sl@0: *clone = *original; sl@0: M3G_ASSIGN_REF(clone->copyOf, original); sl@0: } sl@0: sl@0: *cloneObj = (Object *)clone; sl@0: if (*cloneObj == NULL) { sl@0: return M3G_FALSE; sl@0: } sl@0: sl@0: if (m3gObjectDuplicate(originalObj, cloneObj, pairs, numPairs)) { sl@0: /* Copy image contents */ sl@0: if (original->data) { sl@0: m3gCopyImagePixels(clone, original); sl@0: } sl@0: return M3G_TRUE; sl@0: } sl@0: else { sl@0: return M3G_FALSE; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * sl@0: * \brief Frees the pixel data associated with this image; used for sl@0: * optimizing memory usage after copying the data to a secondary sl@0: * location sl@0: */ sl@0: static void m3gFreeImageData(Image *img) sl@0: { sl@0: M3G_ASSERT(img->format == M3G_RGB || img->format == M3G_LUMINANCE); sl@0: # if !defined(M3G_NGL_TEXTURE_API) sl@0: M3G_ASSERT(!img->mipmapsDirty); sl@0: # endif sl@0: M3G_ASSERT(!img->powerOfTwoDirty); sl@0: M3G_ASSERT(img->powerOfTwo != NULL); sl@0: M3G_ASSERT(!img->pinned); sl@0: sl@0: M3G_LOG1(M3G_LOG_IMAGES, "Freeing copy of image 0x%08X\n", sl@0: (unsigned) img); sl@0: sl@0: if (!img->copyOf) { sl@0: m3gFreeObject(M3G_INTERFACE(img), img->data); sl@0: img->data = 0; sl@0: m3gFreeObject(M3G_INTERFACE(img), img->mipData); sl@0: img->mipData = 0; sl@0: } sl@0: M3G_ASSIGN_REF(img->copyOf, NULL); sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Returns a power-of-two variant of an image sl@0: * sl@0: * This is used for sprites and background images. sl@0: */ sl@0: static Image *m3gGetPowerOfTwoImage(Image *img) sl@0: { sl@0: M3G_VALIDATE_OBJECT(img); sl@0: sl@0: /* Create a power-of-two variant of the image if one doesn't exist sl@0: * already */ sl@0: sl@0: if (img->powerOfTwo == NULL) { sl@0: sl@0: M3Gint width, height; sl@0: M3Gbitmask flags; sl@0: Image *potImage; sl@0: sl@0: M3G_ASSERT(!m3gIsPowerOfTwo(img->width) || sl@0: !m3gIsPowerOfTwo(img->height)); sl@0: sl@0: /* Choose new image size to allow a maximum shrinkage of 25%; sl@0: * this is to weed out pathological cases of quadruple memory sl@0: * usage because an image is one pixel too wide */ sl@0: sl@0: width = m3gNextPowerOfTwo((img->width * 3) >> 2); sl@0: height = m3gNextPowerOfTwo((img->height * 3) >> 2); sl@0: sl@0: width = M3G_MIN(width, M3G_MAX_TEXTURE_DIMENSION); sl@0: height = M3G_MIN(height, M3G_MAX_TEXTURE_DIMENSION); sl@0: sl@0: flags = img->flags & (~M3G_RENDERING_TARGET); sl@0: sl@0: potImage = m3gCreateImage(M3G_INTERFACE(img), sl@0: img->format, sl@0: width, height, sl@0: flags); sl@0: if (!potImage) { sl@0: return NULL; /* automatic out-of-memory */ sl@0: } sl@0: sl@0: M3G_ASSIGN_REF(img->powerOfTwo, potImage); sl@0: img->powerOfTwoDirty = M3G_TRUE; sl@0: } sl@0: sl@0: /* Update POT image data if necessary */ sl@0: sl@0: if (img->powerOfTwoDirty) { sl@0: m3gCopyImagePixels(img->powerOfTwo, img); sl@0: img->powerOfTwoDirty = M3G_FALSE; sl@0: sl@0: /* Get rid of the original at this point if we can */ sl@0: sl@0: if (!img->pinned) { sl@0: m3gFreeImageData(img); sl@0: } sl@0: } sl@0: sl@0: return img->powerOfTwo; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Gets image alpha at x, y. sl@0: * sl@0: * \param image Image object sl@0: * \param x x-coordinate sl@0: * \param y y-coordinate sl@0: * \return alpha value sl@0: * sl@0: */ sl@0: static M3Gint m3gGetAlpha(Image *image, M3Gint x, M3Gint y) sl@0: { sl@0: M3Gint alpha = 255; sl@0: M3Gint bpp = m3gBytesPerPixel(image->internalFormat); sl@0: M3Guint data = 0; sl@0: M3Gubyte *pixels; sl@0: sl@0: /* Quick exit for non-alpha formats */ sl@0: sl@0: if (image->format == M3G_RGB || image->format == M3G_LUMINANCE) { sl@0: return alpha; sl@0: } sl@0: sl@0: /* For other formats, we have to sample the image data */ sl@0: sl@0: if (!image->data) { sl@0: Image *potImage = image->powerOfTwo; sl@0: M3G_ASSERT(potImage != image); sl@0: return m3gGetAlpha(potImage, sl@0: (x * image->width) / potImage->width, sl@0: (y * image->height) / potImage->height); sl@0: } sl@0: sl@0: pixels = ((M3Gubyte *)m3gMapObject(M3G_INTERFACE(image), image->data)); sl@0: sl@0: if (image->paletteBytes == 0) { sl@0: if (bpp == 1) { sl@0: data = pixels[x + y * image->width]; sl@0: } sl@0: else if (bpp == 2) { sl@0: data = ((M3Gushort *)pixels)[x + y * image->width]; sl@0: } sl@0: else { sl@0: data = ((M3Guint *)pixels)[x + y * image->width]; sl@0: } sl@0: } sl@0: else { sl@0: M3Guint *palette; sl@0: palette = (M3Guint *)pixels; sl@0: pixels += image->paletteBytes; sl@0: sl@0: data = palette[pixels[x + y * image->width]]; sl@0: } sl@0: sl@0: m3gUnmapObject(M3G_INTERFACE(image), image->data); sl@0: sl@0: switch (image->internalFormat) { sl@0: sl@0: case M3G_A8: sl@0: alpha = data; sl@0: break; sl@0: case M3G_LA8: sl@0: alpha = data >> 8; sl@0: break; sl@0: case M3G_RGBA8: sl@0: alpha = data >> 24; sl@0: break; sl@0: default: sl@0: /* Should never be here!! */ sl@0: M3G_ASSERT(M3G_FALSE); sl@0: } sl@0: sl@0: return alpha; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Computes the scanline stride of an image sl@0: */ sl@0: static M3Gsizei m3gGetImageStride(const Image *img) sl@0: { sl@0: M3G_VALIDATE_OBJECT(img); sl@0: return img->width * m3gBytesPerPixel(img->internalFormat); sl@0: } sl@0: sl@0: /*---------------------------------------------------------------------- sl@0: * Virtual function table sl@0: *--------------------------------------------------------------------*/ sl@0: sl@0: static const ObjectVFTable m3gvf_Image = { sl@0: m3gObjectApplyAnimation, sl@0: m3gObjectIsCompatible, sl@0: m3gObjectUpdateProperty, sl@0: m3gObjectDoGetReferences, sl@0: m3gObjectFindID, sl@0: m3gImageDuplicate, sl@0: m3gDestroyImage sl@0: }; sl@0: sl@0: sl@0: /*---------------------------------------------------------------------- sl@0: * Public API functions sl@0: *--------------------------------------------------------------------*/ sl@0: sl@0: /*! sl@0: * \brief Creates a new Image sl@0: * sl@0: * \param interface M3G interface sl@0: * \param srcFormat source format sl@0: * \param width width in pixels sl@0: * \param height height in pixels sl@0: * \param flags creation flags; a combination of sl@0: * M3G_DYNAMIC, M3G_STATIC, sl@0: * M3G_RENDERING_TARGET, and M3G_PALETTED sl@0: * \retval Image new Image object sl@0: * \retval NULL Image creating failed sl@0: */ sl@0: M3G_API M3GImage m3gCreateImage(/*@dependent@*/ M3GInterface interface, sl@0: M3GImageFormat srcFormat, sl@0: M3Gint width, M3Gint height, sl@0: M3Gbitmask flags) sl@0: { sl@0: Interface *m3g = (Interface *) interface; sl@0: M3G_VALIDATE_INTERFACE(m3g); sl@0: sl@0: /* Check errors */ sl@0: sl@0: if (width <= 0 || height <= 0) { sl@0: m3gRaiseError(m3g, M3G_INVALID_VALUE); sl@0: return NULL; sl@0: } sl@0: sl@0: if (!m3gInRange(srcFormat, M3G_ALPHA, M3G_RGBA)) { sl@0: m3gRaiseError(m3g, M3G_INVALID_ENUM); sl@0: return NULL; sl@0: } sl@0: sl@0: /* Parameters OK; allocate and initialize the object */ sl@0: sl@0: { sl@0: Image *img = m3gAllocZ(m3g, sizeof(Image)); sl@0: if (img == NULL) { sl@0: return NULL; sl@0: } sl@0: sl@0: /* Clean up and set flags */ sl@0: sl@0: M3G_LOG3(M3G_LOG_IMAGES, "Image 0x%08X is %d x %d", sl@0: (unsigned) img, width, height); sl@0: sl@0: flags |= M3G_DYNAMIC; /* the default */ sl@0: sl@0: if (flags & M3G_STATIC) { sl@0: M3G_LOG(M3G_LOG_IMAGES, ", immutable"); sl@0: flags &= ~M3G_DYNAMIC; sl@0: } sl@0: if (flags & M3G_RENDERING_TARGET) { sl@0: M3G_LOG(M3G_LOG_IMAGES, ", rendertarget"); sl@0: flags |= M3G_DYNAMIC; sl@0: } sl@0: if (flags & M3G_PALETTED) { sl@0: M3G_LOG(M3G_LOG_IMAGES, ", paletted"); sl@0: } sl@0: img->flags = flags; sl@0: sl@0: M3G_LOG(M3G_LOG_IMAGES, "\n"); sl@0: sl@0: { sl@0: /* Allocate pixel & palette data; the palette is stored at sl@0: * the beginning of the pixel data chunk */ sl@0: sl@0: M3Gbool paletted = ((img->flags & M3G_PALETTED) != 0) sl@0: && m3gSupportedPaletteFormat(srcFormat); sl@0: M3GPixelFormat internalFormat = getInternalFormat(srcFormat, sl@0: paletted); sl@0: M3Guint bpp = m3gBytesPerPixel(internalFormat); sl@0: M3Guint pixelBytes = width * height * bpp; sl@0: sl@0: if ((img->flags & M3G_PALETTED) != 0 && !paletted) { sl@0: M3G_LOG(M3G_LOG_WARNINGS|M3G_LOG_IMAGES, sl@0: "Warning: Unsupported paletted format\n"); sl@0: } sl@0: sl@0: /* The palette will always have 256 elements and one byte sl@0: * per color component (except padded 32-bit for NGL) */ sl@0: sl@0: if (paletted) { sl@0: img->paletteBytes = sl@0: # if defined(M3G_NGL_TEXTURE_API) sl@0: 256 * 4; sl@0: # else sl@0: 256 * m3gBytesPerPixel(m3gPixelFormat(srcFormat)); sl@0: # endif sl@0: } sl@0: sl@0: /* Set up the rest of the image parameters */ sl@0: sl@0: img->width = width; sl@0: img->height = height; sl@0: img->format = srcFormat; sl@0: img->internalFormat = internalFormat; sl@0: img->glFormat = m3gGetGLFormat(internalFormat); sl@0: sl@0: M3G_LOG1(M3G_LOG_IMAGES, "Image data %d bytes\n", sl@0: pixelBytes + img->paletteBytes); sl@0: sl@0: /* Allocate the image memory */ sl@0: sl@0: img->data = m3gAllocObject(m3g, pixelBytes + img->paletteBytes); sl@0: if (img->data == 0) { sl@0: m3gFree(m3g, img); sl@0: return NULL; sl@0: } sl@0: sl@0: #ifdef M3G_ENABLE_GLES_RESOURCE_HANDLING sl@0: /* If GLES resource freeing (see function m3gFreeGLESResources) sl@0: is enabled, the GL texture might get deleted at any point, so sl@0: a copy of the texture data has to be always kept in memory. */ sl@0: img->pinned = M3G_TRUE; sl@0: #else sl@0: /* Lock the image data in memory if the image is dynamic, sl@0: * or the format has alpha information; otherwise, we'll sl@0: * be able to get rid of an extra copy when generating a sl@0: * power-of-two version or uploading to OpenGL */ sl@0: sl@0: if ((img->flags & M3G_DYNAMIC) != 0 sl@0: || (img->format != M3G_RGB && sl@0: img->format != M3G_LUMINANCE)) { sl@0: img->pinned = M3G_TRUE; sl@0: } sl@0: #endif sl@0: /* If the image can be used as a rendering target, clear sl@0: * to opaque white by default */ sl@0: sl@0: if ((img->flags & M3G_RENDERING_TARGET) != 0) { sl@0: M3Gubyte *pixels = ((M3Gubyte *)m3gMapObject(m3g, img->data)) sl@0: + img->paletteBytes; sl@0: m3gFill(pixels, (size_t) pixelBytes, -1); sl@0: m3gUnmapObject(m3g, img->data); sl@0: } sl@0: sl@0: /* Check for "special" images that can't be used as sl@0: * textures without some extra trickery */ sl@0: sl@0: if (!m3gIsPowerOfTwo((M3Guint) width) || sl@0: !m3gIsPowerOfTwo((M3Guint) height)) { sl@0: img->special |= IMG_NPOT; sl@0: } sl@0: else { sl@0: img->powerOfTwo = img; sl@0: } sl@0: sl@0: if (width > M3G_MAX_TEXTURE_DIMENSION || sl@0: height > M3G_MAX_TEXTURE_DIMENSION) { sl@0: img->special |= IMG_LARGE; sl@0: } sl@0: } sl@0: sl@0: /* Call base class constructor (can not fail) and return */ sl@0: m3gInitObject(&img->object, m3g, M3G_CLASS_IMAGE); sl@0: sl@0: M3G_VALIDATE_OBJECT(img); sl@0: return (M3GImage) img; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \brief Prevents further modifications to an image sl@0: * sl@0: * Essentially, this changes the default M3G_DYNAMIC flag to sl@0: * M3G_STATIC; this allows the implementation to make memory and sl@0: * performance optimizations not possible for dynamically modified sl@0: * images. sl@0: */ sl@0: M3G_API void m3gCommitImage(M3GImage hImage) sl@0: { sl@0: Image *image = (Image *) hImage; sl@0: M3Gbitmask flags; sl@0: M3G_VALIDATE_OBJECT(image); sl@0: sl@0: flags = image->flags; sl@0: flags &= ~(M3G_DYNAMIC|M3G_RENDERING_TARGET); sl@0: flags |= M3G_STATIC; sl@0: sl@0: image->flags = flags; sl@0: sl@0: #ifndef M3G_ENABLE_GLES_RESOURCE_HANDLING sl@0: /* If the image format has no alpha information, we can discard sl@0: * the image data under suitable conditions */ sl@0: sl@0: if (image->format == M3G_RGB || image->format == M3G_LUMINANCE) { sl@0: image->pinned = M3G_FALSE; sl@0: } sl@0: #endif sl@0: M3G_LOG1(M3G_LOG_IMAGES, "Image 0x%08X made immutable\n", sl@0: (unsigned) image); sl@0: } sl@0: sl@0: /*! sl@0: * \brief Check if image is mutable. sl@0: * sl@0: * \param hImage Image object sl@0: * \retval M3G_TRUE image is mutable sl@0: * \retval M3G_FALSE image is immutable sl@0: */ sl@0: M3G_API M3Gbool m3gIsMutable(M3GImage hImage) sl@0: { sl@0: Image *image = (Image *) hImage; sl@0: M3G_VALIDATE_OBJECT(image); sl@0: return ((image->flags & M3G_DYNAMIC) != 0); sl@0: } sl@0: sl@0: /*! sl@0: * \brief Gets image format as JSR-184 constant sl@0: * sl@0: * \param hImage Image object sl@0: * \return JSR-184 format sl@0: */ sl@0: M3G_API M3GImageFormat m3gGetFormat(M3GImage hImage) sl@0: { sl@0: Image *image = (Image *) hImage; sl@0: M3G_VALIDATE_OBJECT(image); sl@0: return image->format; sl@0: } sl@0: sl@0: /*! sl@0: * \brief Gets image width sl@0: * sl@0: * \param hImage Image object sl@0: * \return width in pixels sl@0: */ sl@0: M3G_API M3Gint m3gGetWidth(M3GImage hImage) sl@0: { sl@0: Image *image = (Image *) hImage; sl@0: M3G_VALIDATE_OBJECT(image); sl@0: return image->width; sl@0: } sl@0: sl@0: /*! sl@0: * \brief Gets image height sl@0: * sl@0: * \param hImage Image object sl@0: * \return height in pixels sl@0: */ sl@0: M3G_API M3Gint m3gGetHeight(M3GImage hImage) sl@0: { sl@0: Image *image = (Image *) hImage; sl@0: M3G_VALIDATE_OBJECT(image); sl@0: return image->height; sl@0: } sl@0: sl@0: /*! sl@0: * \brief Converts a rectangle of pixels of src to dst as srcFormat to sl@0: * dstFormat conversion requires. sl@0: * sl@0: * \param srcFormat source format sl@0: * \param src source pixels sl@0: * \param srcStride source stride sl@0: * \param width width in pixels sl@0: * \param height height in pixels sl@0: * \param dstFormat destination format sl@0: * \param dst destination pixels sl@0: * \param dstStride destination stride sl@0: */ sl@0: static void m3gConvertPixelRect( sl@0: M3GPixelFormat srcFormat, const M3Gubyte *src, M3Gsizei srcStride, sl@0: M3Gsizei width, M3Gsizei height, sl@0: M3GPixelFormat dstFormat, M3Gubyte *dst, M3Gsizei dstStride) sl@0: { sl@0: /* Detect any fast path cases */ sl@0: sl@0: if ((srcFormat == M3G_BGRA8 || srcFormat == M3G_BGR8_32) sl@0: && dstFormat == M3G_RGBA8) { sl@0: if (width > 2 && dstStride == width*4) { sl@0: sl@0: const char endianTest[4] = { 1, 0, 0, 0 }; sl@0: if ((*(const int *)endianTest) == 1) { sl@0: fastConvertBGRAToRGBA(src, srcStride, width, height, dst); sl@0: } sl@0: return; sl@0: } sl@0: } sl@0: sl@0: /* No luck, do the generic conversion */ sl@0: sl@0: while (height-- > 0) { sl@0: m3gConvertPixels(srcFormat, src, dstFormat, dst, width); sl@0: src += srcStride; sl@0: dst += dstStride; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \brief Sets the pixel data for an image sl@0: * sl@0: * \param hImage Image object sl@0: * \param srcPixels source pixels sl@0: */ sl@0: M3G_API void m3gSetImage(M3GImage hImage, const void *srcPixels) sl@0: { sl@0: Image *img = (Image *) hImage; sl@0: M3G_VALIDATE_OBJECT(img); sl@0: sl@0: { sl@0: M3Gsizei bpp = m3gBytesPerPixel(m3gInputDataFormat(img)); sl@0: m3gSetSubImage(hImage, sl@0: 0, 0, img->width, img->height, sl@0: img->width * img->height * bpp, srcPixels); sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \brief Reads pixel data from an image sl@0: * sl@0: * \param hImage Image object sl@0: * \param pixels output buffer for pixels sl@0: */ sl@0: M3G_API void m3gGetImageARGB(M3GImage hImage, M3Guint *pixels) sl@0: { sl@0: Interface *m3g; sl@0: const Image *img = (const Image *) hImage; sl@0: M3G_VALIDATE_OBJECT(img); sl@0: m3g = M3G_INTERFACE(img); sl@0: sl@0: if (!pixels) { sl@0: m3gRaiseError(m3g, M3G_NULL_POINTER); sl@0: return; sl@0: } sl@0: sl@0: if (img->data) { sl@0: const M3Gubyte *src = (const M3Gubyte*) m3gMapObject(m3g, img->data); sl@0: convertToARGB(img->internalFormat, src, sl@0: img->width * img->height, sl@0: pixels); sl@0: m3gUnmapObject(m3g, img->data); sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \brief Sets the palette for an image sl@0: * sl@0: * \param hImage Image object sl@0: * \param paletteLength length of the palette sl@0: * \param srcPalette palette data sl@0: */ sl@0: M3G_API void m3gSetImagePalette(M3GImage hImage, sl@0: M3Gint paletteLength, sl@0: const void *srcPalette) sl@0: { sl@0: Interface *m3g; sl@0: Image *img = (Image *) hImage; sl@0: M3G_VALIDATE_OBJECT(img); sl@0: m3g = M3G_INTERFACE(img); sl@0: sl@0: /* Check for errors */ sl@0: sl@0: if (img->data == 0 || (img->flags & M3G_STATIC) != 0 sl@0: || (img->flags & M3G_PALETTED) == 0) { sl@0: M3G_ASSERT(!(img->flags & M3G_DYNAMIC)); sl@0: m3gRaiseError(m3g, M3G_INVALID_OPERATION); sl@0: return; sl@0: } sl@0: if (srcPalette == NULL) { sl@0: m3gRaiseError(m3g, M3G_NULL_POINTER); sl@0: return; sl@0: } sl@0: if (!m3gInRange(paletteLength, 0, 256)) { sl@0: m3gRaiseError(m3g, M3G_INVALID_VALUE); sl@0: return; sl@0: } sl@0: sl@0: /* sl@0: * Copy the palette data into the allocated palette (for natively sl@0: * supported paletted formats), or remap the existing image data sl@0: * using the supplied palette entries (for non-native formats) sl@0: * sl@0: * NOTE the latter is a one-time operation! sl@0: */ sl@0: if (img->paletteBytes > 0) { sl@0: M3Gubyte *palette = (M3Gubyte *)m3gMapObject(m3g, img->data); sl@0: # if defined(M3G_NGL_TEXTURE_API) sl@0: m3gConvertPixels(m3gPixelFormat(img->format), srcPalette, sl@0: M3G_RGBA8, palette, sl@0: paletteLength); sl@0: # else sl@0: M3Gsizei bpp = m3gBytesPerPixel(m3gPixelFormat(img->format)); sl@0: m3gCopy(palette, srcPalette, (size_t) paletteLength * bpp); sl@0: # endif sl@0: } sl@0: else { sl@0: M3Gint count = img->width * img->height; sl@0: M3Gubyte *pixel = (M3Gubyte*)m3gMapObject(m3g, img->data); sl@0: const M3Gubyte *bytePalette = (const M3Gubyte *) srcPalette; sl@0: sl@0: /* We need to treat the input and internal formats as sl@0: * separate, as the internal storage may be padded to more sl@0: * bytes than there are color components */ sl@0: sl@0: M3GPixelFormat paletteFormat = m3gPixelFormat(img->format); sl@0: const int numComponents = m3gBytesPerPixel(paletteFormat); sl@0: M3GPixelFormat imgFormat = img->internalFormat; sl@0: const int imgBpp = m3gBytesPerPixel(imgFormat); sl@0: sl@0: /* In most cases we can just copy the corresponding palette sl@0: * entry on top of each pixel based on the pixel intensity (R sl@0: * or L component), but special formats require a more sl@0: * complicated conversion. We just use the (slow) general sl@0: * conversion routine, as it already incorporates support for sl@0: * all formats. */ sl@0: sl@0: if (imgBpp >= numComponents) { sl@0: while (count--) { sl@0: int offset = (*pixel) * numComponents; sl@0: int c; sl@0: for (c = 0; c < numComponents; ++c) { sl@0: *pixel++ = bytePalette[offset + c]; sl@0: } sl@0: while (c++ < imgBpp) { /* padding for e.g. 24-bit RGB */ sl@0: *pixel++ = 0xFF; sl@0: } sl@0: } sl@0: } sl@0: else { sl@0: while (count--) { sl@0: int offset = (*pixel) * numComponents; sl@0: m3gConvertPixels(paletteFormat, &bytePalette[offset], sl@0: imgFormat, pixel, sl@0: 1); sl@0: pixel += imgBpp; sl@0: } sl@0: } sl@0: } sl@0: m3gUnmapObject(m3g, img->data); sl@0: m3gInvalidateImage(img); sl@0: } sl@0: sl@0: /*! sl@0: * \brief Sets a scanline of an image sl@0: * sl@0: * \param hImage Image object sl@0: * \param line scanline sl@0: * \param trueAlpha M3G_TRUE if the source image has an alpha channel, sl@0: * M3G_FALSE if it should come from the RGB values; sl@0: * this only matters for alpha-only destination images sl@0: * \param pixels souce pixels sl@0: */ sl@0: M3G_API void m3gSetImageScanline(M3GImage hImage, sl@0: M3Gint line, sl@0: M3Gbool trueAlpha, sl@0: const M3Guint *pixels) sl@0: { sl@0: Image *img = (Image *) hImage; sl@0: M3G_VALIDATE_OBJECT(img); sl@0: sl@0: if (img->data == 0 || (img->flags & M3G_STATIC) != 0 sl@0: || img->paletteBytes != 0) { sl@0: m3gRaiseError(M3G_INTERFACE(img), M3G_INVALID_OPERATION); sl@0: return; sl@0: } sl@0: sl@0: { sl@0: Interface *m3g = M3G_INTERFACE(img); sl@0: M3Gint stride = img->width * m3gBytesPerPixel(img->internalFormat); sl@0: M3Gubyte *dst = ((M3Gubyte *) m3gMapObject(m3g, img->data)) sl@0: + img->paletteBytes; sl@0: sl@0: #ifdef M3G_NGL_TEXTURE_API sl@0: /* For RGB images without alpha channel, source alpha is sl@0: * forced to 0xff. */ sl@0: sl@0: if (img->format == M3G_RGB) { sl@0: M3Gint i; sl@0: M3Guint argb, *dst; sl@0: sl@0: dst = (M3Guint *) pixels; sl@0: sl@0: for (i = 0; i < img->width; i++) { sl@0: argb = *dst | 0xff000000; sl@0: *dst++ = argb; sl@0: } sl@0: } sl@0: #endif sl@0: sl@0: /* Note that an alpha-only destination format is faked for sl@0: * luminance if the source contained no true alpha data; alpha sl@0: * is then inferred from the RGB values instead */ sl@0: sl@0: convertFromARGB(pixels, sl@0: img->width, sl@0: (img->internalFormat == M3G_A8 && !trueAlpha) ? M3G_L8 : img->internalFormat, sl@0: dst + line * stride); sl@0: sl@0: m3gUnmapObject(m3g, img->data); sl@0: m3gInvalidateImage(img); sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \brief Sets a rectangular subregion of an image sl@0: * sl@0: * \param hImage Image object sl@0: * \param x x-coordinate in destination image sl@0: * \param y y-coordinate in destination image sl@0: * \param width width of source pixels sl@0: * \param height height of source pixels sl@0: * \param length length of source data, in bytes sl@0: * \param pixels source pixels sl@0: */ sl@0: M3G_API void m3gSetSubImage(M3GImage hImage, sl@0: M3Gint x, M3Gint y, sl@0: M3Gint width, M3Gint height, sl@0: M3Gint length, const void *pixels) sl@0: { sl@0: Interface *m3g; sl@0: Image *img = (Image *) hImage; sl@0: sl@0: M3GPixelFormat srcFormat; sl@0: M3Gsizei srcBpp; sl@0: sl@0: M3G_VALIDATE_OBJECT(img); sl@0: m3g = M3G_INTERFACE(img); sl@0: sl@0: /* Check for errors */ sl@0: sl@0: if (img->data == 0 || (img->flags & M3G_STATIC) != 0) { sl@0: M3G_ASSERT(!(img->flags & M3G_DYNAMIC)); sl@0: m3gRaiseError(m3g, M3G_INVALID_OPERATION); sl@0: return; sl@0: } sl@0: if (pixels == NULL) { sl@0: m3gRaiseError(m3g, M3G_INVALID_VALUE); sl@0: return; sl@0: } sl@0: if (x < 0 || y < 0 || width <= 0 || height <= 0 sl@0: || x+width > img->width || y+height > img->height) { sl@0: m3gRaiseError(m3g, M3G_INVALID_VALUE); sl@0: return; sl@0: } sl@0: sl@0: srcFormat = m3gInputDataFormat(img); sl@0: srcBpp = m3gBytesPerPixel(srcFormat); sl@0: sl@0: if (length < width * height * srcBpp) { sl@0: m3gRaiseError(m3g, M3G_INVALID_VALUE); sl@0: return; sl@0: } sl@0: sl@0: /* Copy the image data, doing a conversion if the input format sl@0: * does not match the internal storage format */ sl@0: { sl@0: const M3Gubyte *srcPixels = (const M3Gubyte*) pixels; sl@0: M3Gsizei srcStride = width * srcBpp; sl@0: sl@0: M3GPixelFormat dstFormat = img->internalFormat; sl@0: M3Gsizei dstBpp = m3gBytesPerPixel(dstFormat); sl@0: M3Gsizei dstStride = img->width * dstBpp; sl@0: M3Gubyte *dstPixels = sl@0: ((M3Gubyte *)m3gMapObject(m3g, img->data)) sl@0: + img->paletteBytes sl@0: + y * dstStride + x * dstBpp; sl@0: sl@0: M3Gint numLines = height, numPixels = width; sl@0: M3Gbool paletted = (img->flags & M3G_PALETTED) != 0; sl@0: sl@0: /* Optimize the copy for full image width */ sl@0: sl@0: if (width == img->width) { sl@0: numLines = 1; sl@0: numPixels = width * height; sl@0: } sl@0: sl@0: /* Copy a scanline at a time, converting as necessary */ sl@0: sl@0: while (numLines-- > 0) { sl@0: sl@0: /* Matching pixel formats are just copied without sl@0: * conversion, and all internally supported paletted sl@0: * formats match each other physically, so they can be sl@0: * copied as well */ sl@0: sl@0: if (dstFormat == srcFormat || img->paletteBytes > 0) { sl@0: m3gCopy(dstPixels, srcPixels, numPixels * dstBpp); sl@0: } sl@0: else { sl@0: if (!paletted) { sl@0: sl@0: /* Ordinary conversion into an internal format sl@0: * that is encoded differently from the external sl@0: * format; can not be a paletted image */ sl@0: sl@0: M3G_ASSERT((img->flags & M3G_PALETTED) == 0); sl@0: m3gConvertPixels(srcFormat, srcPixels, sl@0: dstFormat, dstPixels, sl@0: numPixels); sl@0: } sl@0: else { sl@0: M3G_ASSERT(!m3gSupportedPaletteFormat(img->format)); sl@0: sl@0: /* Palette indices for one-byte-per-pixel formats sl@0: * are just copied in and mapped to actual values sl@0: * later; multibyte paletted formats require a sl@0: * conversion into LA, RGB, or RGBA format sl@0: * intensity levels temporarily before remapping sl@0: * to actual colors in m3gSetImagePalette */ sl@0: sl@0: if (dstBpp == 1) { sl@0: m3gCopy(dstPixels, srcPixels, numPixels); sl@0: } sl@0: else { sl@0: m3gConvertPixels(M3G_L8, srcPixels, sl@0: dstFormat, dstPixels, sl@0: numPixels); sl@0: } sl@0: } sl@0: } sl@0: sl@0: srcPixels += srcStride; sl@0: dstPixels += dstStride; sl@0: } sl@0: sl@0: /* Release the image data and invalidate mipmap levels */ sl@0: sl@0: m3gUnmapObject(m3g, img->data); sl@0: m3gInvalidateImage(img); sl@0: } sl@0: M3G_VALIDATE_OBJECT(img); sl@0: } sl@0: sl@0: #undef SPAN_BUFFER_SIZE sl@0: