| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352 |
- /*
- ** Command & Conquer Generals(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 <http://www.gnu.org/licenses/>.
- */
- // 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 <stdlib.h>
- #include <stdio.h>
- // 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
|