/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 Electronic Arts Inc.
**
** 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 3 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, see .
*/
// FILE: TexturePage.cpp //////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
//
// Westwood Studios Pacific.
//
// Confidential Information
// Copyright (C) 2001 - All Rights Reserved
//
//-----------------------------------------------------------------------------
//
// Project: ImagePacker
//
// File name: TexturePage.cpp
//
// Created: Colin Day, August 2001
//
// Desc: This class represents a texture that contains packed
// images.
//
//-----------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////
// SYSTEM INCLUDES ////////////////////////////////////////////////////////////
#include
#include
// USER INCLUDES //////////////////////////////////////////////////////////////
#include "Common/Debug.h"
#include "TexturePage.h"
#include "ImagePacker.h"
// DEFINES ////////////////////////////////////////////////////////////////////
// PRIVATE TYPES //////////////////////////////////////////////////////////////
// PRIVATE DATA ///////////////////////////////////////////////////////////////
// PUBLIC DATA ////////////////////////////////////////////////////////////////
// PRIVATE PROTOTYPES /////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// PRIVATE FUNCTIONS //////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// TexturePage::extendToRowIfOpen =============================================
/** If the pixel at location 'row' is "open", then extend the pixel
* at 'src' to that location
*
* NOTE this assumes 'src' and 'row' are pointers into the same
* buffer and the bits per pixel (buffBPP) are treated as the
* same for both */
//=============================================================================
void TexturePage::extendToRowIfOpen( char *src,
Int buffWidth,
Int buffBPP,
Bool extendAlpha,
Int imageHeight,
UnsignedInt fitBits,
Int srcX, Int srcY )
{
char otherAlpha;
char otherColor[ 3 ];
char *row = NULL;
// sanity
if( src == NULL )
return;
//
// try to extend pixel up and down a row as well, we try to extend
// up if we're in the top half of the image down if we're
// in the bottom half of the image. Note we're not
// allowed to extend outside the image region if we don't have
// a border to extend into
//
if( srcY < imageHeight / 2 &&
(srcY != 0 || BitTest( fitBits, ImageInfo::FIT_YBORDER_TOP )) )
{
// try to extend pixel "up" if that pixel is "open"
row = src + (buffWidth * buffBPP);
} // end if
else if( srcY >= imageHeight / 2 &&
(srcY != imageHeight - 1 ||
BitTest( fitBits, ImageInfo::FIT_YBORDER_BOTTOM )) )
{
// try to extend pixel "down" if that pixel is "open"
row = src - (buffWidth * buffBPP);
} // end else
//
// if a 'row' is available, try to extend the current pixel
// into that location if it's open
//
if( row )
{
// read the pixel at 'row'
if( buffBPP == 4 )
{
otherAlpha = row[ 0 ];
otherColor[ 0 ] = row[ 1 ];
otherColor[ 1 ] = row[ 2 ];
otherColor[ 2 ] = row[ 3 ];
} // end if
else
{
otherColor[ 0 ] = row[ 0 ];
otherColor[ 1 ] = row[ 1 ];
otherColor[ 2 ] = row[ 2 ];
} // end else
//
// see if this pixel is "open", again we prefer to check the
// alpha channel if present, otherwise we say black is "open"
//
Bool otherOpen = FALSE;
if( buffBPP == 4 )
{
if( otherAlpha == 0 )
otherOpen = TRUE;
} // end if
else
{
if( otherColor[ 0 ] == 0 &&
otherColor[ 1 ] == 0 &&
otherColor[ 2 ] == 0 )
otherOpen = TRUE;
} // end else
// copy pixel data from 'src' to 'row' if 'row' is "open"
if( otherOpen == TRUE )
{
char alpha;
char color[ 3 ];
// read the pixel data at 'src'
if( buffBPP == 4 )
{
alpha = src[ 0 ];
color[ 0 ] = src[ 1 ];
color[ 1 ] = src[ 2 ];
color[ 2 ] = src[ 3 ];
} // end if
else
{
color[ 0 ] = src[ 0 ];
color[ 1 ] = src[ 1 ];
color[ 2 ] = src[ 2 ];
} // end else
// copy the pixel to 'row'
if( buffBPP == 4 )
{
if( extendAlpha )
row[ 0 ] = alpha;
row[ 1 ] = color[ 0 ];
row[ 2 ] = color[ 1 ];
row[ 3 ] = color[ 2 ];
} // end if
else
{
row[ 0 ] = color[ 0 ];
row[ 1 ] = color[ 1 ];
row[ 2 ] = color[ 2 ];
} // end else
} // end if, other spot is open, copy it
} // end if, row
} // end extendToRowIfOpen
// TexturePage::extendImageEdges ==============================================
/** We want to extend the image data in destBuffer at the location region
* of image->m_pagePos in "outward" directions, effectively bleeding
* the edges outward.
*
* Note we will not extend outward from the image region UNLESS we
* have a border present on that side (described in the image->m_fitBits
* for the region image->m_pagePos).
*/
//=============================================================================
void TexturePage::extendImageEdges( Byte *destBuffer,
Int destWidth,
Int destHeight,
Int destBPP,
ImageInfo *image,
Bool extendAlpha )
{
// sanity
if( destBuffer == NULL || image == NULL )
return;
//
// get the extents that we will loop through on the destination surface,
// those extents are the size of the image, but we have to take into
// account whether or not the destination image was rotated or not
//
Int imageWidth, imageHeight;
if( BitTest( image->m_status, ImageInfo::ROTATED90C ) )
{
imageWidth = image->m_size.y;
imageHeight = image->m_size.x;
} // end if
else
{
imageWidth = image->m_size.x;
imageHeight = image->m_size.y;
} // end else
Int x, y;
char *ptr;
char color[ 3 ];
char alpha;
Bool prevPixel, currPixel;
for( y = 0; y < imageHeight; y++ )
{
// compute beginning of destination row
ptr = destBuffer +
((destHeight - 1 - (image->m_pagePos.lo.y + y)) * destWidth * destBPP ) +
(image->m_pagePos.lo.x * destBPP);
prevPixel = FALSE;
for( x = 0; x < imageWidth; x++ )
{
// read the pixel
if( destBPP == 4 )
{
alpha = ptr[ 0 ];
color[ 0 ] = ptr[ 1 ];
color[ 1 ] = ptr[ 2 ];
color[ 2 ] = ptr[ 3 ];
} // end if
else
{
color[ 0 ] = ptr[ 0 ];
color[ 1 ] = ptr[ 1 ];
color[ 2 ] = ptr[ 2 ];
} // end else
//
// see wheter or not we have data at this pixel, if we have alpha
// we will use just the mask comparison, if not we will compare the
// colors with black (0,0,0) being an "empty" pixel
//
currPixel = FALSE;
if( destBPP == 4 )
{
if( alpha != 0 )
currPixel = TRUE;
} // end if
else
{
if( color[ 0 ] != 0 &&
color[ 1 ] != 0 &&
color[ 2 ] != 0 )
currPixel = TRUE;
} // end else
//
// if we're at the right edge we will extend this pixel off the
// image to the right border if present, we dont' have to worry about
// the top and bottom edges because they are attempted to be
// extended into when we detect an edge change moving across x
//
if( currPixel == TRUE && x == imageWidth - 1 &&
BitTest( image->m_fitBits, ImageInfo::FIT_XBORDER_RIGHT ) )
{
//
// we are at the right side of the image, we have a pixel here,
// AND we have a border to extend that pixel into
//
if( destBPP == 4 )
{
if( extendAlpha )
*(ptr + 4) = alpha;
*(ptr + 5) = color[ 0 ];
*(ptr + 6) = color[ 1 ];
*(ptr + 7) = color[ 2 ];
} // end if
else
{
*(ptr + 3) = color[ 0 ];
*(ptr + 4) = color[ 1 ];
*(ptr + 5) = color[ 2 ];
} // end else
} // end if
//
// if we have a pixel here, attempt to extend it to the above
// or below row if that spot is empty
//
if( currPixel == TRUE )
extendToRowIfOpen( ptr, destWidth, destBPP, extendAlpha,
imageHeight, image->m_fitBits, x, y );
//
// if we've crossed from empty<->filled either extend that pixel
// left or right
//
if( prevPixel == FALSE && currPixel == TRUE )
{
//
// we've crossed from empty to filled, copy the color of current
// pixel to the position of previous pixel. Note this is not allowed
// when we're at the left edge and we DON'T have a border to copy into
//
if( x != 0 || BitTest( image->m_fitBits, ImageInfo::FIT_XBORDER_LEFT ) )
{
// extend our current pixel to the previous location
if( destBPP == 4 && extendAlpha )
*(ptr - 4) = alpha;
*(ptr - 3) = color[ 0 ];
*(ptr - 2) = color[ 1 ];
*(ptr - 1) = color[ 2 ];
} // end if
} // end if
else if( prevPixel == TRUE && currPixel == FALSE )
{
//
// we've crossed from filled to empty, copy the color of previous
// pixel to the current pixel position.
//
//
// this assert should never happen because if x were 0, we are on the
// first column in this image, and the prevPixel should be FALSE since
// previous would be "off" the image which is by definition "open"
//
DEBUG_ASSERTCRASH( x != 0, ("Coming from off image and detecting right edge!") );
// extend the previous pixel to this location
if( destBPP == 4 )
{
if( extendAlpha )
ptr[ 0 ] = *(ptr - 4);
ptr[ 1 ] = *(ptr - 3);
ptr[ 2 ] = *(ptr - 2);
ptr[ 3 ] = *(ptr - 1);
} // end if
else
{
ptr[ 0 ] = *(ptr - 3);
ptr[ 1 ] = *(ptr - 2);
ptr[ 2 ] = *(ptr - 1);
} // end else
} // end else if
//
// one more time now for a special case in the corners of the extended
// image. since this algorithm goes across extending pixels left, right
// up, and down we must check to see if we have a pixel when our
// source location is in the 4 corners of the image ... if so we will
// extend that pixel DIAGONALLY out to make a complete extended rectangle
//
if( currPixel == TRUE )
{
char *dst = NULL;
// top left corner
if( x == 0 && y == 0 &&
BitTest( image->m_fitBits, ImageInfo::FIT_XBORDER_LEFT ) &&
BitTest( image->m_fitBits, ImageInfo::FIT_YBORDER_TOP ) )
dst = (ptr + (destWidth * destBPP)) - destBPP;
// top right corner
else if( x == imageWidth - 1 && y == 0 &&
BitTest( image->m_fitBits, ImageInfo::FIT_XBORDER_RIGHT ) &&
BitTest( image->m_fitBits, ImageInfo::FIT_YBORDER_TOP ) )
dst = (ptr + (destWidth * destBPP)) + destBPP;
// bottom right corner
else if( x == imageWidth - 1 && y == imageHeight - 1 &&
BitTest( image->m_fitBits, ImageInfo::FIT_XBORDER_RIGHT ) &&
BitTest( image->m_fitBits, ImageInfo::FIT_YBORDER_BOTTOM ) )
dst = (ptr - (destWidth * destBPP)) + destBPP;
// bottom left corner
else if( x == 0 && y == imageHeight - 1 &&
BitTest( image->m_fitBits, ImageInfo::FIT_XBORDER_LEFT ) &&
BitTest( image->m_fitBits, ImageInfo::FIT_YBORDER_BOTTOM ) )
dst = (ptr - (destWidth * destBPP)) - destBPP;
// copy the pixel at 'ptr' to 'dst' for the diagonal extend
if( dst )
{
if( destBPP == 4 )
{
if( extendAlpha )
dst[ 0 ] = alpha;
dst[ 1 ] = color[ 0 ];
dst[ 2 ] = color[ 1 ];
dst[ 3 ] = color[ 2 ];
} // end if
else
{
dst[ 0 ] = color[ 0 ];
dst[ 1 ] = color[ 1 ];
dst[ 2 ] = color[ 2 ];
} // end else
} // end if dst
} // end if
// move to the next pixel
ptr += destBPP;
//
// the state of our current pixel (on/off) becomes the state of the
// previous pixel now
//
prevPixel = currPixel;
} // end for x
} // end for y
} // end extendImageEdges
// TexturePage::addImageData ==================================================
/** Add the actual image data from 'image' to the destination buffer
* at the coordinates specified in the 'image' ... this puts the
* actual packed image data on the final texture page
*
* NOTE that we have created our texture page regions with the
* assumption that we were packing images with an upper left
* corner at (0,0), but the targa files have the origin in the
* lower left corner ... thus the translation here to shift source
* images into the right positions
*/
//=============================================================================
Bool TexturePage::addImageData( Byte *destBuffer,
Int destWidth,
Int destHeight,
Int destBPP,
ImageInfo *image )
{
// sanity
if( destBuffer == NULL || image == NULL )
return FALSE;
// load the real image data for the source
Targa source;
if( source.Load( image->m_path, TGAF_IMAGE, FALSE ) != 0 )
{
char buffer[ _MAX_PATH + 32 ];
sprintf( buffer, "Error loading source file '%s'\n", image->m_path );
DEBUG_ASSERTCRASH( 0, (buffer) );
MessageBox( NULL, buffer, "Cannot Load Source File", MB_OK | MB_ICONERROR );
return FALSE;
} // end if
// get the source image buffer
char *sourceBuffer = source.GetImage();
DEBUG_ASSERTCRASH( sourceBuffer, ("No Source buffer for source image\n") );
// get the source bytes per pixel
Int sourceBPP = TGA_BytesPerPixel( source.Header.PixelDepth );
//
// the loaded targas are all laid out flat with no encoding, copy
// all the rows in the source to the destination buffer at the coords
// specified in the images' page location
//
char *src, *dest;
Int count;
Int x, y;
if( BitTest( image->m_status, ImageInfo::ROTATED90C ) == FALSE )
{
//
// normal copy, image was not rotated
//
// do all rows
for( y = 0; y < image->m_size.y; y++ )
{
// compute source location
src = sourceBuffer + ( (image->m_size.y - 1 - y) * image->m_size.x * sourceBPP);
// compute destination location
dest = destBuffer +
((destHeight - 1 - (image->m_pagePos.lo.y + y)) * destWidth * destBPP ) +
(image->m_pagePos.lo.x * destBPP);
// copy a row from source to destination
count = image->m_pagePos.hi.x - image->m_pagePos.lo.x + 1;
for( x = 0; x < count; x++ )
{
// check the target and source formats
if( destBPP == 4 )
{
// copy the rgb
dest[ 3 ] = src[ 0 ];
dest[ 2 ] = src[ 1 ];
dest[ 1 ] = src[ 2 ];
// copy the alpha if present in the source
if( sourceBPP == 4 )
dest[ 0 ] = src[ 3 ];
else
dest[ 0 ] = (char)0xFF; // solid alpha
} // end if
else
{
// copy the rgb
dest[ 2 ] = src[ 0 ];
dest[ 1 ] = src[ 1 ];
dest[ 0 ] = src[ 2 ];
} // end else
// skip past all these pixels
dest += destBPP;
src += sourceBPP;
} // end for x
} // end for y
} // end if, not rotated
else
{
//
// image was rotated, perform a 90 degrees rotation clockwise when we
// copy over the image data
//
for( y = 0; y < image->m_size.y; y++ )
{
// compute beginning of source row to copy from
src = sourceBuffer + ((image->m_size.y - 1 - y) * image->m_size.x * sourceBPP);
// for each pixel in source put it in dest rotated
for( x = 0; x < image->m_size.x; x++ )
{
// compute destination location
dest = destBuffer +
( ( (destHeight - 1) - (image->m_pagePos.lo.y + x) ) * destWidth * destBPP ) +
((image->m_pagePos.lo.x + (image->m_size.y - 1 - y)) * destBPP);
// copy this pixel, checking target and source formats
if( destBPP == 4 )
{
// copy the rgb
dest[ 3 ] = src[ 0 ];
dest[ 2 ] = src[ 1 ];
dest[ 1 ] = src[ 2 ];
// copy the alpha if present in the source
if( sourceBPP == 4 )
dest[ 0 ] = src[ 3 ];
else
dest[ 0 ] = (char)0xFF; // solid alpha
} // end if
else
{
// copy the rgb
dest[ 2 ] = src[ 0 ];
dest[ 1 ] = src[ 1 ];
dest[ 0 ] = src[ 2 ];
} // end else
// skip past all these pixels
dest += destBPP;
src += sourceBPP;
} // end for x
} // end for y
} // end else
//
// if we have the option to extend the RGB edges on we now need to process
// the image we just copied into the texture page and "bleed" the edges outward
// and if a border is present, into the border
//
if( BitTest( TheImagePacker->getGapMethod(),
ImagePacker::GAP_METHOD_EXTEND_RGB ) )
extendImageEdges( destBuffer,
destWidth,
destHeight,
destBPP,
image,
FALSE );
return TRUE; // all done
} // end addImageData
// TexturePage::spotUsed ======================================================
/** Is this spot in the texture page open? */
//=============================================================================
Bool TexturePage::spotUsed( Int x, Int y )
{
return m_canvas[ y * m_size.y + x ];
} // end spotUsed
// TexturePage::lineUsed ======================================================
/** Is there ANY spot in the line specified that is used */
//=============================================================================
Bool TexturePage::lineUsed( Int sx, Int sy, Int ex, Int ey )
{
Int x, y;
UnsignedByte *ptr;
for( y = sy; y <= ey; y++ )
{
// compute start of row
ptr = m_canvas + (y * m_size.y + sx);
// scan the row
for( x = sx; x <= ex; x++, ptr++ )
if( *ptr == USED )
return USED;
} // end for y
return FALSE; // it's open!
} // end lineUsed
// TexturePage::markRegionUsed ================================================
/** Mark this region as used */
//=============================================================================
void TexturePage::markRegionUsed( IRegion2D *region )
{
UnsignedByte *ptr;
Int y;
Int count;
// loop through y
for( y = region->lo.y; y <= region->hi.y; y++ )
{
// compute start of row
ptr = m_canvas + (y * m_size.y + region->lo.x);
// fill this row
count = (region->hi.x - region->lo.x) + 1;
memset( ptr, USED, count );
} // end for
} // end markRegionUsed
// TexturePage::buildFitRegion ================================================
/** Build an image region to try to fit into the page based on the location
* given, with the image size, the gutter sizes, and the all sides border
* size
*
* Note that x and y Gutter sizes can be changed as a result of this
* method
*
* Returns a set of "fit bits" that describe what the components
* have been included in the region constructed
*/
//=============================================================================
UnsignedInt TexturePage::buildFitRegion( IRegion2D *region,
Int startX, Int startY,
Int imageWidth, Int imageHeight,
Int *xGutter, Int *yGutter,
Bool allSidesBorder )
{
// sanity
if( region == NULL || xGutter == NULL || yGutter == NULL )
return 0;
//
// create border size, if we have an 'allSidesBorder' then we need to
// add two pixels to width and height
//
Int xBorder = 0, yBorder = 0;
if( allSidesBorder )
{
xBorder = 2;
yBorder = 2;
} // end if
//
// when the image size exactly matches the target size of the texture
// page the region will not include any gutter or border sizes cause
// we can fit the image exactly as it is on the page
//
if( imageWidth == m_size.x )
{
*xGutter = 0;
xBorder = 0;
} // end if
if( imageHeight == m_size.y )
{
*yGutter = 0;
yBorder = 0;
} // end if
//
// when an image is 1 pixel smaller than the destination texture
// page we can eliminate some borders used to stretch the RGB values
// at the edges because we will be hitting one side of the texture
// anyway. We will say these borders that are only on one side
// will be on the right and bottom (as described in the fit bits
// returned below)
//
if( imageWidth == m_size.x - 1 )
xBorder = 1;
if( imageHeight == m_size.y - 1 )
yBorder = 1;
// build the region
region->lo.x = startX;
region->lo.y = startY;
region->hi.x = startX + imageWidth - 1 + *xGutter + xBorder; // -1 for zero based
region->hi.y = startY + imageHeight - 1 + *yGutter + yBorder; // -1 for zero based
//
// build a set of region bit flags that tell what this region ACTUALLY
// used in its construction. Note that when we stripped off only ONE
// pixel off one dimension when talking about borders, we say that the
// border that we added was either on the right side of the image,
// or on the bottom of the image
//
UnsignedInt fitBits = 0;
if( *xGutter != 0 )
BitSet( fitBits, ImageInfo::FIT_XGUTTER );
if( *yGutter != 0 )
BitSet( fitBits, ImageInfo::FIT_YGUTTER );
if( xBorder >= 1 )
BitSet( fitBits, ImageInfo::FIT_XBORDER_RIGHT );
if( xBorder == 2 )
BitSet( fitBits, ImageInfo::FIT_XBORDER_LEFT );
if( yBorder >= 1 )
BitSet( fitBits, ImageInfo::FIT_YBORDER_BOTTOM );
if( yBorder == 2 )
BitSet( fitBits, ImageInfo::FIT_YBORDER_TOP );
return fitBits;
} // end buildFitRegion
///////////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// TexturePage::TexturePage ===================================================
/** */
//============================================================================
TexturePage::TexturePage( Int width, Int height )
{
Int canvasSize;
m_id = -1;
m_next = NULL;
m_prev = NULL;
m_size.x = width;
m_size.y = height;
m_packedImage = NULL;
m_targa = NULL;
// create a "canvas" to represent used and unused areas
canvasSize = m_size.x * m_size.y;
m_canvas = new UnsignedByte[ canvasSize ];
DEBUG_ASSERTCRASH( m_canvas, ("Cannot allocate canvas for texture page\n") );
memset( m_canvas, FREE, sizeof( UnsignedByte ) * canvasSize );
} // end TexturePage
// TexturePage::~TexturePage ==================================================
/** */
//=============================================================================
TexturePage::~TexturePage( void )
{
// delete the canvas
if( m_canvas )
delete [] m_canvas;
// delete targa if present, this will NOT delete a user assigned image buffer
if( m_targa )
delete m_targa;
// delete the final image buffer if present
if( m_packedImage )
delete [] m_packedImage;
} // end ~TexturePage
// TexturePage::addImage ======================================================
/** If this image will fit on this page, add it */
//=============================================================================
Bool TexturePage::addImage( ImageInfo *image )
{
IRegion2D region;
// santiy
if( image == NULL )
{
DEBUG_ASSERTCRASH( image, ("TexturePage::addImage: NULL image!\n") );
return TRUE; // say it was added
} // end if
// get our options for fitting
Bool useGutter, useRGBExtend;
useGutter = BitTest( TheImagePacker->getGapMethod(),
ImagePacker::GAP_METHOD_GUTTER );
useRGBExtend = BitTest( TheImagePacker->getGapMethod(),
ImagePacker::GAP_METHOD_EXTEND_RGB );
//
// try to fit this image in this page ... we have two tries, once
// normally, and once with the image rotated 90 degrees clockwise
//
Int triesLeft = 2;
Bool tryRotate = FALSE;
while( triesLeft )
{
// try the rotated image the second time around
if( triesLeft == 1 )
tryRotate = TRUE;
// we've now 'used up' a try
triesLeft--;
// find a free upper left corner to put the image in
Int x, y;
Int xGutter = 0, yGutter = 0;
Int imageWidth, imageHeight;
UnsignedInt fitBits = 0;
for( y = 0; y < m_size.y; y++ )
{
for( x = 0; x < m_size.x; x++ )
{
// get the gutter size
if( useGutter )
{
xGutter = TheImagePacker->getGutter();
yGutter = TheImagePacker->getGutter();
} // end if
else
{
xGutter = 0;
yGutter = 0;
} // end else
//
// compute the region of the image at this location, the region that will
// be used will take up the image region AND the gutter. UNLESS the
// image is as big as the texture page, in that case there is no reason
// to use a gutter size. Also note that if we're trying to fit
// a rotated image this time we have to swap the coords
// around a little bit
//
if( tryRotate == FALSE )
{
// normal, non-rotated image
imageWidth = image->m_size.x;
imageHeight = image->m_size.y;
} // end if
else
{
//
// build region for rotation 90 degrees clockwise
//
// 1------------2
// | |
// 3------------4
//
// becomes
//
// 3--1
// | |
// | |
// | |
// | |
// | |
// 4--2
//
imageWidth = image->m_size.y;
imageHeight = image->m_size.x;
} // end else
// build the region
fitBits = buildFitRegion( ®ion, x, y,
imageWidth, imageHeight,
&xGutter, &yGutter,
useRGBExtend );
//
// if the image region plus the gutter goes off the image page, BUT
// the image itself stays on the page, adjust the gutter to only
// be as big as from the end of the image to the end of the page,
// this technically doesn't fill the requirements of making a gutter
// around every image, but it's OK since that space will be
// designated as filled, and at that will be filled with
// transparent alpha - nothingness!
//
if( region.hi.x >= m_size.x )
{
//
// attempt to shrink x gutter if image can still fit with a
// smaller gutter to the right
//
if( region.hi.x - xGutter < m_size.x )
xGutter = m_size.x - imageWidth;
// rebuild region with new gutter size
fitBits = buildFitRegion( ®ion, x, y,
imageWidth, imageHeight,
&xGutter, &yGutter,
useRGBExtend );
} // end if
if( region.hi.y >= m_size.y )
{
//
// attempt to shrink y gutter if image can still fit with
// a smaller gutter below
//
if( region.hi.y - yGutter < m_size.y )
yGutter = m_size.y - imageHeight;
// rebuild region with new gutter size
fitBits = buildFitRegion( ®ion, x, y,
imageWidth, imageHeight,
&xGutter, &yGutter,
useRGBExtend );
} // end if
// reject this location if the hi region goes off the texture page
if( region.hi.y >= m_size.y )
{
y = m_size.y; // skip to end, this isn't gonna work
continue;
} // end if
if( region.hi.x >= m_size.x )
{
x = m_size.x; // skip to end of row to try next row
continue;
} // end if
//
// reject this location if any of the corners are in used spots,
// note since we're trying to fit things by "sliding" them
// horizontally ... we cut out more checks if we see if the horizontal
// bounds are full at the right side first ... if they are we
// move our "anchor" point to try to fit again to after that
// point (saving the checking of between the current upper
// left anchor point and the right side point that is used anyway
//
// upper right and lower right
if( spotUsed( region.hi.x, region.lo.y ) || // upper right
spotUsed( region.hi.x, region.hi.y ) ) // lower right
{
x = region.hi.x; // next anchor spot will be to the right of here
continue;
} // end if
// upper left and lower left
if( spotUsed( region.lo.x, region.lo.y ) || // upper left
spotUsed( region.lo.x, region.hi.y ) ) // lower left
continue;
//
// reject this location if we cross any used locations between
// the 4 corners
//
if( lineUsed( region.lo.x, region.lo.y,
region.hi.x, region.lo.y ) || // top side
lineUsed( region.hi.x, region.lo.y,
region.hi.x, region.hi.y ) || // right side
lineUsed( region.lo.x, region.hi.y,
region.hi.x, region.hi.y ) || // bottom side
lineUsed( region.lo.x, region.lo.y,
region.lo.x, region.hi.y ) ) // left side
continue;
//
// we passed all tests, take up this spot
//
markRegionUsed( ®ion ); // marks region AND gutter used
BitClear( image->m_status, ImageInfo::TOOBIG );
BitClear( image->m_status, ImageInfo::UNPACKED );
BitSet( image->m_status, ImageInfo::PACKED );
image->m_page = this;
//
// store the properties of the region that was used to fit this
// image
//
image->m_fitBits = fitBits;
// store the gutter sizes used in fitting this image
image->m_gutterUsed.x = xGutter;
image->m_gutterUsed.y = yGutter;
//
// if we packed this image rotated, set a flag telling us we
// need to swap the size dimension in the image structure
// when copying the image data
//
if( tryRotate == TRUE )
BitSet( image->m_status, ImageInfo::ROTATED90C );
//
// save the page position of this image, but do not include
// the gutter or padding borders which is incorporated into the region,
// we're interested in just the bounding rectangle of the image itself
// on the texture page
//
image->m_pagePos = region;
if( BitTest( fitBits, ImageInfo::FIT_XBORDER_LEFT ) )
image->m_pagePos.lo.x++;
if( BitTest( fitBits, ImageInfo::FIT_YBORDER_TOP ) )
image->m_pagePos.lo.y++;
if( BitTest( fitBits, ImageInfo::FIT_XBORDER_RIGHT ) )
image->m_pagePos.hi.x--;
if( BitTest( fitBits, ImageInfo::FIT_YBORDER_BOTTOM ) )
image->m_pagePos.hi.y--;
if( BitTest( fitBits, ImageInfo::FIT_XGUTTER ) )
image->m_pagePos.hi.x -= xGutter;
if( BitTest( fitBits, ImageInfo::FIT_YGUTTER ) )
image->m_pagePos.hi.y -= yGutter;
// link this image to the texture page
image->m_prevPageImage = NULL;
image->m_nextPageImage = m_imageList;
if( m_imageList )
m_imageList->m_prevPageImage = image;
m_imageList = image;
return TRUE; // success
} // end for x
} // end for y
} // end while, triesLeft
// no space
return FALSE;
} // end addImage
// TexturePage::generateTexture ===============================================
/** Generate the final packed texture given all the images that have
* already been assigned to this page */
//=============================================================================
Bool TexturePage::generateTexture( void )
{
// sanity
if( m_imageList == NULL )
return FALSE;
// sanity
DEBUG_ASSERTCRASH( m_packedImage == NULL, ("The packed image list must be NULL before generating texture\n") );
DEBUG_ASSERTCRASH( m_targa == NULL, ("The targa must be NULL before generating a new texture\n") );
// allocate targa to help us generate the final texture
m_targa = new Targa;
if( m_targa == NULL )
{
char buffer[ 128 ];
sprintf( buffer, "Unable to allocate new targa to generate texture\n" );
DEBUG_ASSERTCRASH( m_targa, (buffer) );
MessageBox( NULL, buffer, "Internal Error", MB_OK | MB_ICONERROR );
return FALSE;
} // end if
Bool outputAlpha = TheImagePacker->getOutputAlpha();
Int depth, bpp;
//
// if we're outputting an alpha channel we will use 32 bit color depth,
// if we're not we will use 24 bit
//
if( outputAlpha )
depth = 32;
else
depth = 24;
// how many bytes per pixel for the targa file format
bpp = TGA_BytesPerPixel( depth );
// allocate a buffer for our final image
Int bufferSize = m_size.x * m_size.y * bpp;
m_packedImage = new Byte[ bufferSize ];
if( m_packedImage == NULL )
{
char buffer[ 128 ];
sprintf( buffer, "Unable to allocate final packed image buffer\n" );
DEBUG_ASSERTCRASH( m_packedImage, (buffer) );
MessageBox( NULL, buffer, "Internal Error", MB_OK | MB_ICONERROR );
BitSet( m_status, PAGE_ERROR );
BitSet( m_status, CANT_ALLOCATE_PACKED_IMAGE );
return FALSE;
} // end if
// zero the packed image to all zero
memset( m_packedImage, 0, sizeof( Byte ) * bufferSize );
// setup the targa header
m_targa->Header.ImageType = TGA_TRUECOLOR;
m_targa->Header.Width = m_size.x;
m_targa->Header.Height = m_size.y;
m_targa->Header.PixelDepth = depth;
// add all the images to the final packed buffer
ImageInfo *image;
for( image = m_imageList; image; image = image->m_nextPageImage )
{
if( addImageData( m_packedImage, m_size.x, m_size.y,
bpp, image ) == FALSE )
{
BitSet( m_status, PAGE_ERROR );
BitSet( m_status, CANT_ADD_IMAGE_DATA );
return FALSE;
} // end if
} // end for image
// set this data into the targa structure
m_targa->SetImage( m_packedImage );
return TRUE; // success
} // end generateTexture
// TexturePage::writeFile =====================================================
/** Write the texture data that has already been generated to a file
* starting with the baseName passed in with the texture id of
* this texture page appended to the end of it */
//=============================================================================
Bool TexturePage::writeFile( char *baseFilename )
{
// sanity
if( baseFilename == NULL || m_targa == NULL )
{
BitSet( m_status, PAGE_ERROR );
BitSet( m_status, NO_TEXTURE_DATA );
return FALSE;
} // end if
// construct filename
char filePath[ _MAX_PATH ];
sprintf( filePath, "%s%s_%03d.tga", TheImagePacker->getOutputDirectory(),
baseFilename, getID() );
// write the file
Bool error = FALSE;
long flags = TGAF_IMAGE;
if( TheImagePacker->getCompressTextures() == TRUE )
BitSet( flags, TGAF_COMPRESS );
error = m_targa->Save( filePath, flags , FALSE );
if( error != 0 )
{
// there was an error, set a status bit
BitSet( m_status, PAGE_ERROR );
BitSet( m_status, ERROR_DURING_SAVE );
} // end if
// return success or not
return !error;
} // end writeFile
// TexturePage::getPixel ======================================================
/** Get the RGB pixel stored at location (x,y) (where (0,0) is the upper
* left corner of the image ... even though the internal targa
* isn't stored that way */
//=============================================================================
void TexturePage::getPixel( Int x, Int y, Byte *r, Byte *g, Byte *b, Byte *a )
{
// do nothing if we have no image data
if( m_packedImage == NULL )
return;
// how many bytes per pixel for the targa file format
Int depth = m_targa->Header.PixelDepth;
Int bpp = TGA_BytesPerPixel( depth );
// compute location into buffer
Byte *buf;
buf = m_packedImage + ((m_size.y - 1 - y) * m_size.x * bpp) + (x * bpp);
// read the pixel data
if( bpp == 4 )
{
if( a )
*a = buf[ 0 ];
*r = buf[ 1 ];
*g = buf[ 2 ];
*b = buf[ 3 ];
} // end if
else
{
if( a )
*a = (char)0xff; // no data, just return solid alpha
*r = buf[ 0 ];
*g = buf[ 1 ];
*b = buf[ 2 ];
} // end else
} // end getPixel