Back to index

lightning-sunbird  0.9+nobinonly
nsImageMac.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either of the GNU General Public License Version 2 or later (the "GPL"),
00026  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 #include "nsImageMac.h"
00039 #include "nsRenderingContextMac.h"
00040 #include "nsDeviceContextMac.h"
00041 #include "nsRegionPool.h"
00042 #include "prmem.h"
00043 
00044 #include <limits.h>
00045 
00046 /* CoreGraphics limitation with flipped CTM surfaces: height must be less than signed 16-bit max */
00047 #define CG_MAX_HEIGHT   SHRT_MAX
00048 #define CG_MAX_WIDTH    USHRT_MAX
00049 
00050 // wrapper for CGContextDrawImage to protect against large-image calls - see bug 328258, 399286
00051 static void CallCGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image) {
00052   if ( rect.size.width <= CG_MAX_WIDTH && rect.size.height <= CG_MAX_HEIGHT ) {
00053     ::CGContextDrawImage(c, rect, image);
00054   }
00055 }
00056 
00057 
00058 // Number of bits for each component in a pixel.
00059 #define BITS_PER_COMPONENT  8
00060 // Number of components per pixel (i.e. as in ARGB).
00061 #define COMPS_PER_PIXEL     4
00062 // Number of bits in a pixel.
00063 #define BITS_PER_PIXEL      BITS_PER_COMPONENT * COMPS_PER_PIXEL
00064 
00065 // MacOSX 10.2 supports CGPattern, which does image/pattern tiling for us, and
00066 // is much faster than doing it ourselves.
00067 #define USE_CGPATTERN_TILING
00068 
00069 #if 0
00070 
00071 // useful region debugging code.
00072 static OSStatus PrintRgnRectProc(UInt16 message, RgnHandle rgn, const Rect *inRect, void *refCon)
00073 {
00074   UInt32*   rectCount = (UInt32*)refCon;
00075   
00076   switch (message)
00077   {
00078     case kQDRegionToRectsMsgInit:
00079       printf("Dumping region 0x%X\n", rgn);
00080       break;
00081       
00082     case kQDRegionToRectsMsgParse:
00083       printf("Rect %d t,l,r,b: %ld, %ld, %ld, %ld\n", *rectCount, inRect->top, inRect->left, inRect->right, inRect->bottom);
00084       (*rectCount)++;
00085       break;
00086       
00087     case kQDRegionToRectsMsgTerminate:
00088       printf("\n");
00089       break;
00090   }
00091   
00092   return noErr;
00093 }
00094 
00095 static void PrintRegionOutline(RgnHandle inRgn)
00096 {
00097   static RegionToRectsUPP sCountRectProc = nsnull;
00098   if (!sCountRectProc)
00099     sCountRectProc = NewRegionToRectsUPP(PrintRgnRectProc);
00100   
00101   UInt32    rectCount = 0;  
00102   ::QDRegionToRects(inRgn, kQDParseRegionFromTopLeft, sCountRectProc, &rectCount);
00103 }
00104 #endif
00105 
00106 #pragma mark -
00107 
00108 /**********************************************************
00109     nsImageMac
00110  **********************************************************/
00111 nsImageMac::nsImageMac()
00112 : mImageBits(nsnull)
00113 , mImage(nsnull)
00114 , mWidth(0)
00115 , mHeight(0)
00116 , mRowBytes(0)
00117 , mBytesPerPixel(0)   // this value is never initialized; the API that uses it is unused
00118 , mAlphaBits(nsnull)
00119 , mAlphaRowBytes(0)
00120 , mAlphaDepth(0)
00121 , mPendingUpdate(PR_FALSE)
00122 , mOptimized(PR_FALSE)
00123 , mDecodedX1(PR_INT32_MAX)
00124 , mDecodedY1(PR_INT32_MAX)
00125 , mDecodedX2(0)
00126 , mDecodedY2(0)
00127 {
00128 }
00129 
00130 
00131 nsImageMac::~nsImageMac()
00132 {
00133   if (mImage)
00134     ::CGImageRelease(mImage);
00135 
00136   if (mImageBits)
00137     PR_Free(mImageBits);
00138 
00139   if (mAlphaBits)
00140     PR_Free(mAlphaBits);
00141 }
00142 
00143 
00144 NS_IMPL_ISUPPORTS3(nsImageMac, nsIImage, nsIImageMac,
00145                    nsIImageMac_MOZILLA_1_8_BRANCH)
00146 
00147 
00148 nsresult
00149 nsImageMac::Init(PRInt32 aWidth, PRInt32 aHeight, PRInt32 aDepth,
00150                  nsMaskRequirements aMaskRequirements)
00151 {
00152   // Assumed: Init only runs once (due to gfxIImageFrame only allowing 1 Init)
00153 
00154   mWidth = aWidth;
00155   mHeight = aHeight;
00156 
00157   switch (aMaskRequirements)
00158   {
00159     case nsMaskRequirements_kNeeds1Bit:
00160       mAlphaDepth = 1;
00161       break;
00162 
00163     case nsMaskRequirements_kNeeds8Bit:
00164       mAlphaDepth = 8;
00165       break;
00166 
00167     case nsMaskRequirements_kNoMask:
00168     default:
00169       break; // avoid compiler warning
00170   }
00171 
00172   // create the memory for the image
00173   // 24-bit images are 8 bits per component; the alpha component is ignored
00174   mRowBytes = CalculateRowBytes(aWidth, aDepth == 24 ? 32 : aDepth);
00175   mImageBits = (PRUint8*) PR_Malloc(mHeight * mRowBytes * sizeof(PRUint8));
00176   if (!mImageBits)
00177     return NS_ERROR_OUT_OF_MEMORY;
00178 
00179   if (mAlphaDepth)
00180   {
00181     mAlphaRowBytes = CalculateRowBytes(aWidth, mAlphaDepth);
00182     mAlphaBits = (PRUint8*) PR_Malloc(mHeight * mAlphaRowBytes * sizeof(PRUint8));
00183     if (!mAlphaBits) {
00184       PR_Free(mImageBits);
00185       mImageBits = nsnull;
00186       return NS_ERROR_OUT_OF_MEMORY;
00187     }
00188   }
00189 
00190   return NS_OK;
00191 }
00192 
00193 
00194 // The image bits (mImageBits & mAlphaBits) have been updated, so set the
00195 // mPendingUpdate flag to force the recreation of CGImageRef later.  This is a
00196 // lazy update, as ImageUpdated() can be called several times in a row, but
00197 // we only recreate the CGImageRef when needed.
00198 void
00199 nsImageMac::ImageUpdated(nsIDeviceContext *aContext, PRUint8 aFlags,
00200                          nsRect *aUpdateRect)
00201 {
00202   mPendingUpdate = PR_TRUE;
00203 
00204   mDecodedX1 = PR_MIN(mDecodedX1, aUpdateRect->x);
00205   mDecodedY1 = PR_MIN(mDecodedY1, aUpdateRect->y);
00206 
00207   if (aUpdateRect->YMost() > mDecodedY2)
00208     mDecodedY2 = aUpdateRect->YMost();
00209 
00210   if (aUpdateRect->XMost() > mDecodedX2)
00211     mDecodedX2 = aUpdateRect->XMost();
00212 }
00213 
00217 PRBool nsImageMac::GetIsImageComplete() {
00218   return mDecodedX1 == 0 &&
00219          mDecodedY1 == 0 &&
00220          mDecodedX2 == mWidth &&
00221          mDecodedY2 == mHeight;
00222 }
00223 
00224 void DataProviderReleaseFunc(void *info, const void *data, size_t size)
00225 {
00226   PR_Free(NS_CONST_CAST(void*, data));
00227 }
00228 
00229 
00230 // Create CGImageRef from image bits. Merge alpha bit into mImageBits, which
00231 // contains "place holder" for the alpha information.  Then, create the
00232 // CGImageRef for use in drawing.
00233 nsresult
00234 nsImageMac::EnsureCachedImage()
00235 {
00236   // Only create cached image if mPendingUpdate is set.
00237   if (!mPendingUpdate)
00238     return NS_OK;
00239 
00240   if (mImage) {
00241     ::CGImageRelease(mImage);
00242     mImage = NULL;
00243   }
00244 
00245   PRUint8* imageData = NULL;  // data from which to create CGImage
00246   CGImageAlphaInfo alphaInfo; // alpha info for CGImage
00247   void (*releaseFunc)(void *info, const void *data, size_t size) = NULL;
00248 
00249   switch (mAlphaDepth)
00250   {
00251     case 8:
00252     {
00253       // For 8-bit alpha, we create our own storage, since we premultiply the
00254       // alpha info into the image bits, but we still want to keep the original
00255       // image bits (mImageBits).
00256       imageData = (PRUint8*) PR_Malloc(mWidth * mHeight * COMPS_PER_PIXEL);
00257       if (!imageData)
00258         return NS_ERROR_OUT_OF_MEMORY;
00259       PRUint8* tmp = imageData;
00260 
00261       for (PRInt32 y = 0; y < mHeight; y++)
00262       {
00263         PRInt32 rowStart = mRowBytes * y;
00264         PRInt32 alphaRowStart = mAlphaRowBytes * y;
00265         for (PRInt32 x = 0; x < mWidth; x++)
00266         {
00267           // Here we combine the alpha information with each pixel component,
00268           // creating an image with 'premultiplied alpha'.
00269           PRUint8 alpha = mAlphaBits[alphaRowStart + x];
00270           *tmp++ = alpha;
00271           PRUint32 offset = rowStart + COMPS_PER_PIXEL * x;
00272           FAST_DIVIDE_BY_255(*tmp++, mImageBits[offset + 1] * alpha);
00273           FAST_DIVIDE_BY_255(*tmp++, mImageBits[offset + 2] * alpha);
00274           FAST_DIVIDE_BY_255(*tmp++, mImageBits[offset + 3] * alpha);
00275         }
00276       }
00277 
00278       // The memory that we pass to the CGDataProvider needs to stick around as
00279       // long as the provider does, and the provider doesn't get destroyed until
00280       // the CGImage is destroyed.  So we have this small function which is
00281       // called when the provider is destroyed.
00282       releaseFunc = DataProviderReleaseFunc;
00283       alphaInfo = kCGImageAlphaPremultipliedFirst;
00284       break;
00285     }
00286 
00287     case 1:
00288     {
00289       for (PRInt32 y = 0; y < mHeight; y++)
00290       {
00291         PRInt32 rowStart = mRowBytes * y;
00292         PRUint8* alphaRow = mAlphaBits + mAlphaRowBytes * y;
00293         for (PRInt32 x = 0; x < mWidth; x++)
00294         {
00295           // Copy the alpha information into the place holder in the image data.
00296           mImageBits[rowStart + COMPS_PER_PIXEL * x] =
00297                                             GetAlphaBit(alphaRow, x) ? 255 : 0;
00298         }
00299       }
00300 
00301       alphaInfo = kCGImageAlphaPremultipliedFirst;
00302       imageData = mImageBits;
00303       break;
00304     }
00305 
00306     case 0:
00307     default:
00308     {
00309       alphaInfo = kCGImageAlphaNoneSkipFirst;
00310       imageData = mImageBits;
00311       break;
00312     }
00313   }
00314 
00315   CGColorSpaceRef cs = ::CGColorSpaceCreateDeviceRGB();
00316   StColorSpaceReleaser csReleaser(cs);
00317   CGDataProviderRef prov = ::CGDataProviderCreateWithData(NULL, imageData,
00318                                                           mRowBytes * mHeight,
00319                                                           releaseFunc);
00320   mImage = ::CGImageCreate(mWidth, mHeight, BITS_PER_COMPONENT, BITS_PER_PIXEL,
00321                            mRowBytes, cs, alphaInfo, prov, NULL, TRUE,
00322                            kCGRenderingIntentDefault);
00323   ::CGDataProviderRelease(prov);
00324 
00325   mPendingUpdate = PR_FALSE;
00326   return NS_OK;
00327 }
00328 
00329 
00330 NS_IMETHODIMP
00331 nsImageMac::Draw(nsIRenderingContext &aContext, nsIDrawingSurface* aSurface,
00332                  PRInt32 aSX, PRInt32 aSY, PRInt32 aSWidth, PRInt32 aSHeight,
00333                  PRInt32 aDX, PRInt32 aDY, PRInt32 aDWidth, PRInt32 aDHeight)
00334 {
00335   if (mDecodedX2 < mDecodedX1 || mDecodedY2 < mDecodedY1)
00336     return NS_OK;
00337 
00338   nsresult rv = EnsureCachedImage();
00339   NS_ENSURE_SUCCESS(rv, rv);
00340 
00341   PRInt32 srcWidth = aSWidth;
00342   PRInt32 srcHeight = aSHeight;
00343   PRInt32 dstWidth = aDWidth;
00344   PRInt32 dstHeight = aDHeight;
00345 
00346   // Ensure that the source rect is no larger than our decoded rect.
00347   // If it is, clip the source rect, and scale the difference to adjust
00348   // the dest rect.
00349 
00350   PRInt32 j = aSX + aSWidth;
00351   PRInt32 z;
00352   if (j > mDecodedX2) {
00353     z = j - mDecodedX2;
00354     aDWidth -= z*dstWidth/srcWidth;
00355     aSWidth -= z;
00356   }
00357   if (aSX < mDecodedX1) {
00358     aDX += (mDecodedX1 - aSX)*dstWidth/srcWidth;
00359     aSX = mDecodedX1;
00360   }
00361 
00362   j = aSY + aSHeight;
00363   if (j > mDecodedY2) {
00364     z = j - mDecodedY2;
00365     aDHeight -= z*dstHeight/srcHeight;
00366     aSHeight -= z;
00367   }
00368   if (aSY < mDecodedY1) {
00369     aDY += (mDecodedY1 - aSY)*dstHeight/srcHeight;
00370     aSY = mDecodedY1;
00371   }
00372 
00373   if (aDWidth <= 0 || aDHeight <= 0 || aSWidth <= 0 || aSHeight <= 0)
00374     return NS_OK;
00375 
00376   nsDrawingSurfaceMac* surface = static_cast<nsDrawingSurfaceMac*>(aSurface);
00377   CGContextRef context = surface->StartQuartzDrawing();
00378 
00379   CGRect srcRect = ::CGRectMake(aSX, aSY, aSWidth, aSHeight);
00380   CGRect destRect = ::CGRectMake(aDX, aDY, aDWidth, aDHeight);
00381 
00382   CGRect drawRect = ::CGRectMake(0, 0, mWidth, mHeight);
00383   if (!::CGRectEqualToRect(srcRect, destRect))
00384   {
00385     // If drawing a portion of the image, change the drawRect accordingly.
00386     float sx = ::CGRectGetWidth(destRect) / ::CGRectGetWidth(srcRect);
00387     float sy = ::CGRectGetHeight(destRect) / ::CGRectGetHeight(srcRect);
00388     float dx = ::CGRectGetMinX(destRect) - (::CGRectGetMinX(srcRect) * sx);
00389     float dy = ::CGRectGetMinY(destRect) - (::CGRectGetMinY(srcRect) * sy);
00390     drawRect = ::CGRectMake(dx, dy, mWidth * sx, mHeight * sy);
00391   }
00392 
00393   ::CGContextClipToRect(context, destRect);
00394   CallCGContextDrawImage(context, drawRect, mImage);
00395   surface->EndQuartzDrawing(context);
00396 
00397   return NS_OK;
00398 }
00399 
00400 
00401 NS_IMETHODIMP
00402 nsImageMac::Draw(nsIRenderingContext &aContext, 
00403                  nsIDrawingSurface* aSurface,
00404                  PRInt32 aX, PRInt32 aY, 
00405                  PRInt32 aWidth, PRInt32 aHeight)
00406 {
00407 
00408   return Draw(aContext, aSurface, 0, 0, mWidth, mHeight, aX, aY, aWidth, aHeight);
00409 }
00410 
00411 
00412 NS_IMETHODIMP
00413 nsImageMac::DrawToImage(nsIImage* aDstImage, PRInt32 aDX, PRInt32 aDY,
00414                         PRInt32 aDWidth, PRInt32 aDHeight)
00415 {
00416   nsImageMac* dest = NS_STATIC_CAST(nsImageMac*, aDstImage);
00417 
00418   nsresult rv = EnsureCachedImage();
00419   NS_ENSURE_SUCCESS(rv, rv);
00420   rv = dest->EnsureCachedImage();
00421   NS_ENSURE_SUCCESS(rv, rv);
00422 
00423   rv = NS_ERROR_FAILURE;
00424 
00425   // Create image storage.  The storage is 'owned' by the CGImageRef created
00426   // below as 'newImageRef'; that is, the storage cannot be deleted until the
00427   // CGImageRef is released.
00428   PRInt32 width = dest->GetWidth();
00429   PRInt32 height = dest->GetHeight();
00430   PRInt32 bytesPerRow = dest->GetLineStride();
00431   PRInt32 totalBytes = height * bytesPerRow;
00432   PRUint8* bitmap = (PRUint8*) PR_Malloc(totalBytes);
00433   if (!bitmap)
00434     return NS_ERROR_OUT_OF_MEMORY;
00435 
00436   CGColorSpaceRef cs = ::CGColorSpaceCreateDeviceRGB();
00437   StColorSpaceReleaser csReleaser(cs);
00438   CGContextRef bitmapContext =
00439                   ::CGBitmapContextCreate(bitmap, width, height,
00440                                           BITS_PER_COMPONENT, bytesPerRow,
00441                                           cs, kCGImageAlphaPremultipliedFirst);
00442 
00443   if (bitmapContext)
00444   {
00445     CGRect destRect = ::CGRectMake(0, 0, width, height);
00446     CGRect drawRect = ::CGRectMake(aDX, aDY, aDWidth, aDHeight);
00447 
00448     // clear the bitmap context
00449     ::CGContextClearRect(bitmapContext, destRect);
00450 
00451     // draw destination and then this image into bitmap
00452     CallCGContextDrawImage(bitmapContext, destRect, dest->mImage);
00453     CallCGContextDrawImage(bitmapContext, drawRect, mImage);
00454 
00455     ::CGContextRelease(bitmapContext);
00456 
00457     CGImageAlphaInfo alphaInfo = ::CGImageGetAlphaInfo(dest->mImage);
00458 
00459     // create a new image from the combined bitmap
00460     CGDataProviderRef prov = ::CGDataProviderCreateWithData(NULL, bitmap,
00461                                                             totalBytes, NULL);
00462     CGImageRef newImageRef = ::CGImageCreate(width, height, BITS_PER_COMPONENT,
00463                                              BITS_PER_PIXEL, bytesPerRow, cs,
00464                                              alphaInfo, prov, NULL, TRUE,
00465                                              kCGRenderingIntentDefault);
00466     if (newImageRef)
00467     {
00468       // set new image in destination
00469       dest->AdoptImage(newImageRef, bitmap);
00470       ::CGImageRelease(newImageRef);
00471       rv = NS_OK;
00472     }
00473 
00474     ::CGDataProviderRelease(prov);
00475   }
00476 
00477   return rv;
00478 }
00479 
00480 void
00481 nsImageMac::AdoptImage(CGImageRef aNewImage, PRUint8* aNewBitmap)
00482 {
00483   NS_PRECONDITION(aNewImage != nsnull && aNewBitmap != nsnull,
00484                   "null ptr");
00485   if (!aNewImage || !aNewBitmap)
00486     return;
00487 
00488   // Free exising image and image bits.
00489   ::CGImageRelease(mImage);
00490   if (mImageBits)
00491     PR_Free(mImageBits);
00492 
00493   // Adopt given image and image bits.
00494   mImage = aNewImage;
00495   ::CGImageRetain(mImage);
00496   mImageBits = aNewBitmap;
00497 }
00498 
00499 
00500 #pragma mark -
00501 
00502 // Frees up memory from any unneeded structures.
00503 nsresult
00504 nsImageMac::Optimize(nsIDeviceContext* aContext)
00505 {
00506   nsresult rv = EnsureCachedImage();
00507   NS_ENSURE_SUCCESS(rv, rv);
00508 
00509   // We cannot delete the bitmap data from which mImage was created;  the data
00510   // needs to stick around at least as long as the mImage is valid.
00511   // mImageBits is used by all images, except those that have mAlphaDepth == 8
00512   // (for which we created a separate data store in EnsureCachedImage).
00513   if (mImageBits && mAlphaDepth == 8) {
00514     PR_Free(mImageBits);
00515     mImageBits = NULL;
00516   }
00517 
00518   // mAlphaBits is not really used any more after EnsureCachedImage is called,
00519   // since it's data is merged into mImageBits.
00520   if (mAlphaBits) {
00521     PR_Free(mAlphaBits);
00522     mAlphaBits = NULL;
00523   }
00524 
00525   mOptimized = PR_TRUE;
00526 
00527   return NS_OK;
00528 }
00529 
00530 
00531 NS_IMETHODIMP
00532 nsImageMac::LockImagePixels(PRBool aMaskPixels)
00533 {
00534   if (!mOptimized)
00535     return NS_OK;
00536 
00537   // Need to recreate mAlphaBits and/or mImageBits.  We do so by drawing the
00538   // CGImage into a bitmap context.  Afterwards, 'imageBits' contains the
00539   // image data with premultiplied alpha (if applicable).
00540   PRUint8* imageBits = (PRUint8*) PR_Malloc(mHeight * mRowBytes);
00541   if (!imageBits)
00542     return NS_ERROR_OUT_OF_MEMORY;
00543   CGColorSpaceRef cs = ::CGColorSpaceCreateDeviceRGB();
00544   StColorSpaceReleaser csReleaser(cs);
00545   CGContextRef bitmapContext =
00546       ::CGBitmapContextCreate(imageBits, mWidth, mHeight, BITS_PER_COMPONENT,
00547                               mRowBytes, cs, kCGImageAlphaPremultipliedFirst);
00548   if (!bitmapContext) {
00549     PR_Free(imageBits);
00550     return NS_ERROR_FAILURE;
00551   }
00552 
00553   // clear the bitmap context & draw mImage into it
00554   CGRect drawRect = ::CGRectMake(0, 0, mWidth, mHeight);
00555   ::CGContextClearRect(bitmapContext, drawRect);
00556   CallCGContextDrawImage(bitmapContext, drawRect, mImage);
00557   ::CGContextRelease(bitmapContext);
00558 
00559   // 'imageBits' now contains the image and (possibly) alpha bits for image.
00560   // Now we need to separate them out.
00561 
00562   if (mAlphaDepth) {
00563     // Only need to worry about alpha for mAlphaDepth == 1 or 8.
00564     mAlphaBits = (PRUint8*) PR_Malloc(mHeight * mAlphaRowBytes *
00565                                       sizeof(PRUint8));
00566     if (!mAlphaBits) {
00567       PR_Free(imageBits);
00568       return NS_ERROR_OUT_OF_MEMORY;
00569     }
00570   }
00571 
00572   switch (mAlphaDepth)
00573   {
00574     case 8:
00575     {
00576       // Need to recreate mImageBits for mAlphaDepth == 8 only.
00577       // See comments nsImageMac::Optimize().
00578       PRUint8* tmp = imageBits;
00579       mImageBits = (PRUint8*) PR_Malloc(mHeight * mRowBytes * sizeof(PRUint8));
00580       if (!mImageBits) {
00581         PR_Free(mAlphaBits);
00582         mAlphaBits = nsnull;
00583         PR_Free(imageBits);
00584         return NS_ERROR_OUT_OF_MEMORY;
00585       }
00586 
00587       // split the alpha bits and image bits into their own structure
00588       for (PRInt32 y = 0; y < mHeight; y++)
00589       {
00590         PRInt32 rowStart = mRowBytes * y;
00591         PRInt32 alphaRowStart = mAlphaRowBytes * y;
00592         for (PRInt32 x = 0; x < mWidth; x++)
00593         {
00594           PRUint8 alpha = *tmp++;
00595           mAlphaBits[alphaRowStart + x] = alpha;
00596           PRUint32 offset = rowStart + COMPS_PER_PIXEL * x;
00597           if (alpha) {
00598             mImageBits[offset + 1] = ((PRUint32) *tmp++) * 255 / alpha;
00599             mImageBits[offset + 2] = ((PRUint32) *tmp++) * 255 / alpha;
00600             mImageBits[offset + 3] = ((PRUint32) *tmp++) * 255 / alpha;
00601           }
00602           else {
00603             tmp += 3;
00604             mImageBits[offset + 1] =
00605              mImageBits[offset + 2] =
00606              mImageBits[offset + 3] = 0;
00607           }
00608         }
00609       }
00610       break;
00611     }
00612 
00613     // mImageBits still exists for mAlphaDepth == 1 or mAlphaDepth == 0,
00614     // since the bits are owned by the CGImageRef.  So the only thing to
00615     // do is to recreate the alpha bits for mAlphaDepth == 1.
00616 
00617     case 1:
00618     {
00619       // recreate the alpha bits structure
00620       for (PRInt32 y = 0; y < mHeight; y++)
00621       {
00622         PRInt32 rowStart = mRowBytes * y;
00623         PRUint8* alphaRow = mAlphaBits + mAlphaRowBytes * y;
00624         for (PRInt32 x = 0; x < mWidth; x++)
00625         {
00626           if (imageBits[rowStart + COMPS_PER_PIXEL * x])
00627             SetAlphaBit(alphaRow, x);
00628           else
00629             ClearAlphaBit(alphaRow, x);
00630         }
00631       }
00632     }
00633 
00634     case 0:
00635     default:
00636       break;
00637   }
00638 
00639   PR_Free(imageBits);
00640   return NS_OK;
00641 }
00642 
00643 
00644 NS_IMETHODIMP
00645 nsImageMac::UnlockImagePixels(PRBool aMaskPixels)
00646 {
00647   if (mOptimized)
00648     Optimize(nsnull);
00649 
00650   return NS_OK;
00651 }
00652 
00653 
00654 #pragma mark -
00655 
00673 PRInt32 nsImageMac::CalculateRowBytesInternal(PRUint32 aWidth, PRUint32 aDepth, PRBool aAllow2Bytes)
00674 {
00675     PRInt32 rowBits = aWidth * aDepth;
00676     //if bits per row is 24 or less, may need 3 bytes or less
00677     return (rowBits > 24 || !aAllow2Bytes) ?
00678         ((aWidth * aDepth + 31) / 32) * 4 :
00679         ((aWidth * aDepth + 15) / 16) * 2;
00680 }
00681 
00682 
00687 PRInt32 nsImageMac::CalculateRowBytes(PRUint32 aWidth, PRUint32 aDepth)
00688 {
00689     return CalculateRowBytesInternal(aWidth, aDepth, PR_FALSE);
00690 }
00691 
00692 
00693 PRBool nsImageMac::RenderingToPrinter(nsIRenderingContext &aContext)
00694 {
00695   nsCOMPtr<nsIDeviceContext> dc;                   // (this screams for a private interface, sigh!)
00696   aContext.GetDeviceContext(*getter_AddRefs(dc));
00697   // a weird and wonderful world of scanky casts and oddly-named intefaces.
00698   nsDeviceContextMac* theDevContext = NS_REINTERPRET_CAST(nsDeviceContextMac*, dc.get());
00699   return theDevContext && theDevContext->IsPrinter();
00700 }
00701 
00702 
00703 #pragma mark -
00704 
00705 //
00706 // ConvertToPICT
00707 //
00708 // Convert from image bits to a PICT, probably for placement on the clipboard.
00709 // Draw the image into a temporary GWorld, and then blit it into the picture.
00710 //
00711 // XXX TODO In the future, clipboard operations should be converted to using
00712 // the Pasteboard Manager.  Then we could simply write our image to a PDF
00713 // context and put that on the clipboard.
00714 //
00715 NS_IMETHODIMP
00716 nsImageMac::ConvertToPICT(PicHandle* outPicture)
00717 {
00718   *outPicture = nsnull;
00719   Rect picFrame  = {0, 0, mHeight, mWidth};
00720 
00721   PRUint32 pixelDepth = ::CGImageGetBitsPerPixel(mImage);
00722 
00723   // allocate a "normal" GWorld (which owns its own pixels)
00724   GWorldPtr tempGWorld = NULL;
00725   ::NewGWorld(&tempGWorld, pixelDepth, &picFrame, nsnull, nsnull, 0);
00726   if (!tempGWorld)
00727     return NS_ERROR_FAILURE;
00728 
00729   PixMapHandle tempPixMap = ::GetGWorldPixMap(tempGWorld);
00730   if (tempPixMap)
00731   {
00732     StPixelLocker tempPixLocker(tempPixMap);      // locks the pixels
00733 
00734     // erase it to white
00735     {
00736       StGWorldPortSetter setter(tempGWorld);
00737       ::BackColor(whiteColor);
00738       ::EraseRect(&picFrame);
00739     }
00740 
00741     // set as current GWorld
00742     GWorldPtr currPort;
00743     GDHandle currDev;
00744     ::GetGWorld(&currPort, &currDev);
00745     ::SetGWorld(tempGWorld, nsnull);
00746 
00747     ::SetOrigin(0, 0);
00748     ::ForeColor(blackColor);
00749     ::BackColor(whiteColor);
00750 
00751     // Get the storage addr for the GWorld and create a bitmap context from it.
00752     // Then we simply call CGContextDrawImage to draw our image into the GWorld.
00753     // NOTE: The width of the created context must be a multiple of 4.
00754     PRUint8* bitmap = (PRUint8*) ::GetPixBaseAddr(tempPixMap);
00755 
00756     PRUint32 bytesPerPixel = ::CGImageGetBitsPerPixel(mImage) / 8;
00757     PRUint32 bitmapWidth = (mWidth + 4) & ~0x3;
00758     PRUint32 bitmapRowBytes = bitmapWidth * bytesPerPixel;
00759 
00760     CGColorSpaceRef cs = ::CGColorSpaceCreateDeviceRGB();
00761     StColorSpaceReleaser csReleaser(cs);
00762     CGContextRef bitmapContext =
00763                     ::CGBitmapContextCreate(bitmap, bitmapWidth, mHeight,
00764                                             BITS_PER_COMPONENT,
00765                                             bitmapRowBytes, cs,
00766                                             kCGImageAlphaPremultipliedFirst);
00767 
00768     NS_ASSERTION(bitmapContext, "Failed to create bitmap context");
00769 
00770     if (bitmapContext)
00771     {
00772       // Translate to QuickDraw coordinate system
00773       ::CGContextTranslateCTM(bitmapContext, 0, mHeight);
00774       ::CGContextScaleCTM(bitmapContext, 1, -1);
00775 
00776       // Draw image into GWorld
00777       CGRect drawRect = ::CGRectMake(0, 0, mWidth, mHeight);
00778       CallCGContextDrawImage(bitmapContext, drawRect, mImage);
00779       ::CGContextRelease(bitmapContext);
00780 
00781       PicHandle thePicture = ::OpenPicture(&picFrame);
00782       if (thePicture)
00783       {
00784         // blit image from GWorld into Picture
00785         ::CopyBits(::GetPortBitMapForCopyBits(tempGWorld),
00786                    ::GetPortBitMapForCopyBits(tempGWorld),
00787                    &picFrame, &picFrame, ditherCopy, nsnull);
00788 
00789         ::ClosePicture();
00790         if (QDError() == noErr)
00791           *outPicture = thePicture;
00792       }
00793     }
00794 
00795     ::SetGWorld(currPort, currDev);     // restore to the way things were
00796   }
00797 
00798   ::DisposeGWorld(tempGWorld);        // do this after dtor of tempPixLocker!
00799 
00800   return *outPicture ? NS_OK : NS_ERROR_FAILURE;
00801 }
00802 
00803 
00804 NS_IMETHODIMP
00805 nsImageMac::ConvertFromPICT(PicHandle inPicture)
00806 {
00807   NS_WARNING("ConvertFromPICT is not implemented.");
00808   return NS_ERROR_NOT_IMPLEMENTED;
00809 }
00810 
00811 
00812 NS_IMETHODIMP
00813 nsImageMac::GetCGImageRef(CGImageRef* aCGImageRef)
00814 {
00815   nsresult rv = EnsureCachedImage();
00816   if (NS_FAILED(rv)) return rv;
00817 
00818   *aCGImageRef = mImage;
00819   return NS_OK;
00820 }
00821 
00822 
00823 #pragma mark -
00824 
00825 nsresult
00826 nsImageMac::SlowTile(nsIRenderingContext &aContext, nsIDrawingSurface* aSurface,
00827                      PRInt32 aSXOffset, PRInt32 aSYOffset,
00828                      PRInt32 aPadX, PRInt32 aPadY, const nsRect &aTileRect)
00829 {
00830   PRInt32
00831     validX = 0,
00832     validY = 0,
00833     validWidth  = mWidth,
00834     validHeight = mHeight;
00835   
00836   // limit the image rectangle to the size of the image data which
00837   // has been validated.
00838   if (mDecodedY2 < mHeight) {
00839     validHeight = mDecodedY2 - mDecodedY1;
00840   }
00841   if (mDecodedX2 < mWidth) {
00842     validWidth = mDecodedX2 - mDecodedX1;
00843   }
00844   if (mDecodedY1 > 0) {   
00845     validHeight -= mDecodedY1;
00846     validY = mDecodedY1;
00847   }
00848   if (mDecodedX1 > 0) {
00849     validWidth -= mDecodedX1;
00850     validX = mDecodedX1; 
00851   }
00852 
00853   PRInt32 aY0 = aTileRect.y - aSYOffset,
00854           aX0 = aTileRect.x - aSXOffset,
00855           aY1 = aTileRect.y + aTileRect.height,
00856           aX1 = aTileRect.x + aTileRect.width;
00857 
00858   for (PRInt32 y = aY0; y < aY1; y += mHeight + aPadY)
00859     for (PRInt32 x = aX0; x < aX1; x += mWidth + aPadX)
00860     {
00861       Draw(aContext, aSurface,
00862            0, 0, PR_MIN(validWidth, aX1-x), PR_MIN(validHeight, aY1-y),     // src coords
00863            x, y, PR_MIN(validWidth, aX1-x), PR_MIN(validHeight, aY1-y));    // dest coords
00864     }
00865 
00866   return NS_OK;
00867 }
00868 
00869 
00870 nsresult
00871 nsImageMac::DrawTileQuickly(nsIRenderingContext &aContext,
00872                             nsIDrawingSurface* aSurface,
00873                             PRInt32 aSXOffset, PRInt32 aSYOffset,
00874                             const nsRect &aTileRect)
00875 {
00876   CGColorSpaceRef cs = ::CGColorSpaceCreateDeviceRGB();
00877   StColorSpaceReleaser csReleaser(cs);
00878 
00879   PRUint32 tiledCols = (aTileRect.width + aSXOffset + mWidth - 1) / mWidth;
00880   PRUint32 tiledRows = (aTileRect.height + aSYOffset + mHeight - 1) / mHeight;
00881 
00882   // XXX note that this code tiles the entire image, even when we just
00883   // need a small portion, which can get expensive
00884   PRUint32 bitmapWidth = tiledCols * mWidth;
00885   PRUint32 bitmapHeight = tiledRows * mHeight;
00886   PRUint32 bitmapRowBytes = tiledCols * mRowBytes;
00887   PRUint32 totalBytes = bitmapHeight * bitmapRowBytes;
00888   PRUint8* bitmap = (PRUint8*) PR_Malloc(totalBytes);
00889   if (!bitmap)
00890     return NS_ERROR_OUT_OF_MEMORY;
00891 
00892   CGContextRef bitmapContext;
00893   bitmapContext = ::CGBitmapContextCreate(bitmap, bitmapWidth, bitmapHeight,
00894                                           BITS_PER_COMPONENT, bitmapRowBytes,
00895                                           cs, kCGImageAlphaPremultipliedFirst);
00896 
00897   if (bitmapContext != NULL)
00898   {
00899     // clear the bitmap context
00900     ::CGContextClearRect(bitmapContext,
00901                          ::CGRectMake(0, 0, bitmapWidth, bitmapHeight));
00902   
00903     // prime bitmap with initial tile draw
00904     // Draw image into 'bottom' of bitmap, to make the calculations in the
00905     // following for loops somewhat simpler.
00906     CGRect drawRect = ::CGRectMake(0, bitmapHeight - mHeight, mWidth, mHeight);
00907     CallCGContextDrawImage(bitmapContext, drawRect, mImage);
00908     ::CGContextRelease(bitmapContext);
00909 
00910     // Manually blit image, doubling each time.
00911     // Quartz does not provide any functions for blitting from a context to a
00912     // context, so rather than create a CFImageRef each time through the loop
00913     // in order to blit it to the context, we just do it manually.
00914     PRUint32 tileHeight = mHeight;
00915     for (PRUint32 destCol = 1; destCol < tiledCols; destCol *= 2)
00916     {
00917       PRUint8* srcLine = bitmap;
00918       PRUint32 bytesToCopy = destCol * mRowBytes;
00919       PRUint8* destLine = srcLine + bytesToCopy;
00920       if (destCol * 2 > tiledCols)
00921       {
00922         bytesToCopy = (tiledCols - destCol) * mRowBytes;
00923       }
00924       for (PRUint32 row = 0; row < tileHeight; row++)
00925       {
00926         memcpy(destLine, srcLine, bytesToCopy);
00927         srcLine += bitmapRowBytes;
00928         destLine += bitmapRowBytes;
00929       }
00930     }
00931 
00932     for (PRUint32 destRow = 1; destRow < tiledRows; destRow *= 2)
00933     {
00934       PRUint32 tileRowBytes = mHeight * bitmapRowBytes;
00935       PRUint32 bytesToCopy = destRow * tileRowBytes;
00936       PRUint8* dest = bitmap + bytesToCopy;
00937       if (destRow * 2 > tiledRows)
00938       {
00939         bytesToCopy = (tiledRows - destRow) * tileRowBytes;
00940       }
00941       memcpy(dest, bitmap, bytesToCopy);
00942     }
00943 
00944     // Create final tiled image from bitmap
00945     CGDataProviderRef prov = ::CGDataProviderCreateWithData(NULL, bitmap,
00946                                                             totalBytes, NULL);
00947     CGImageRef tiledImage = ::CGImageCreate(bitmapWidth, bitmapHeight,
00948                                             BITS_PER_COMPONENT, BITS_PER_PIXEL,
00949                                             bitmapRowBytes, cs,
00950                                             kCGImageAlphaPremultipliedFirst,
00951                                             prov, NULL, TRUE,
00952                                             kCGRenderingIntentDefault);
00953     ::CGDataProviderRelease(prov);
00954 
00955     nsDrawingSurfaceMac* surface = static_cast<nsDrawingSurfaceMac*>(aSurface);
00956     CGContextRef context = surface->StartQuartzDrawing();
00957 
00958     CGRect srcRect = ::CGRectMake(aSXOffset, aSYOffset, aTileRect.width,
00959                                   aTileRect.height);
00960     CGRect destRect = ::CGRectMake(aTileRect.x, aTileRect.y, aTileRect.width,
00961                                    aTileRect.height);
00962 
00963     drawRect = ::CGRectMake(0, 0, bitmapWidth, bitmapHeight);
00964     if (!::CGRectEqualToRect(srcRect, destRect))
00965     {
00966       // If drawing a portion of the image, change the drawRect accordingly.
00967       float sx = ::CGRectGetWidth(destRect) / ::CGRectGetWidth(srcRect);
00968       float sy = ::CGRectGetHeight(destRect) / ::CGRectGetHeight(srcRect);
00969       float dx = ::CGRectGetMinX(destRect) - (::CGRectGetMinX(srcRect) * sx);
00970       float dy = ::CGRectGetMinY(destRect) - (::CGRectGetMinY(srcRect) * sy);
00971       drawRect = ::CGRectMake(dx, dy, bitmapWidth * sx, bitmapHeight * sy);
00972     }
00973 
00974     ::CGContextClipToRect(context, destRect);
00975     CallCGContextDrawImage(context, drawRect, tiledImage);
00976 
00977     ::CGImageRelease(tiledImage);
00978     surface->EndQuartzDrawing(context);
00979   }
00980 
00981   PR_Free(bitmap);
00982 
00983   return NS_OK;
00984 }
00985 
00986 void
00987 DrawTileAsPattern(void *aInfo, CGContextRef aContext)
00988 {
00989   CGImageRef image = static_cast<CGImageRef> (aInfo);
00990 
00991   float width = ::CGImageGetWidth(image);
00992   float height = ::CGImageGetHeight(image);
00993   CGRect drawRect = ::CGRectMake(0, 0, width, height);
00994   CallCGContextDrawImage(aContext, drawRect, image);
00995 }
00996 
00997 nsresult
00998 nsImageMac::DrawTileWithQuartz(nsIDrawingSurface* aSurface,
00999                                PRInt32 aSXOffset, PRInt32 aSYOffset,
01000                                PRInt32 aPadX, PRInt32 aPadY,
01001                                const nsRect &aTileRect)
01002 {
01003   nsDrawingSurfaceMac* surface = static_cast<nsDrawingSurfaceMac*>(aSurface);
01004   CGContextRef context = surface->StartQuartzDrawing();
01005   ::CGContextSaveGState(context);
01006 
01007   static const CGPatternCallbacks callbacks = {0, &DrawTileAsPattern, NULL};
01008 
01009   // get the current transform from the context
01010   CGAffineTransform patternTrans = CGContextGetCTM(context);
01011   patternTrans = CGAffineTransformTranslate(patternTrans, aTileRect.x, aTileRect.y);
01012 
01013   CGPatternRef pattern;
01014   pattern = ::CGPatternCreate(mImage, ::CGRectMake(0, 0, mWidth, mHeight),
01015                               patternTrans, mWidth + aPadX, mHeight + aPadY,
01016                               kCGPatternTilingConstantSpacing,
01017                               TRUE, &callbacks);
01018 
01019   CGColorSpaceRef patternSpace = ::CGColorSpaceCreatePattern(NULL);
01020   ::CGContextSetFillColorSpace(context, patternSpace);
01021   ::CGColorSpaceRelease(patternSpace);
01022 
01023   float alpha = 1.0f;
01024   ::CGContextSetFillPattern(context, pattern, &alpha);
01025   ::CGPatternRelease(pattern);
01026   
01027   // set the pattern phase (really -ve x and y offsets, but we negate
01028   // the y offset again to take flipping into account)
01029   ::CGContextSetPatternPhase(context, CGSizeMake(-aSXOffset, aSYOffset));
01030 
01031   CGRect tileRect = ::CGRectMake(aTileRect.x,
01032                                  aTileRect.y,
01033                                  aTileRect.width,
01034                                  aTileRect.height);
01035 
01036   ::CGContextFillRect(context, tileRect);
01037   
01038   ::CGContextRestoreGState(context);
01039   surface->EndQuartzDrawing(context);
01040   return NS_OK;
01041 }
01042 
01043 
01044 NS_IMETHODIMP nsImageMac::DrawTile(nsIRenderingContext &aContext,
01045                                    nsIDrawingSurface* aSurface,
01046                                    PRInt32 aSXOffset, PRInt32 aSYOffset,
01047                                    PRInt32 aPadX, PRInt32 aPadY,
01048                                    const nsRect &aTileRect)
01049 {
01050   nsresult rv = EnsureCachedImage();
01051   NS_ENSURE_SUCCESS(rv, rv);
01052 
01053   if (mDecodedX2 < mDecodedX1 || mDecodedY2 < mDecodedY1)
01054     return NS_OK;
01055 
01056   PRUint32 tiledCols = (aTileRect.width + aSXOffset + mWidth - 1) / mWidth;
01057   PRUint32 tiledRows = (aTileRect.height + aSYOffset + mHeight - 1) / mHeight;
01058 
01059   // The tiling that we do below can be expensive for large background
01060   // images, for which we have few tiles.  Plus, the SlowTile call eventually
01061   // calls the Quartz drawing functions, which can take advantage of hardware
01062   // acceleration.  So if we have few tiles, we just call SlowTile.
01063 #ifdef USE_CGPATTERN_TILING
01064   const PRUint32 kTilingCopyThreshold = 16;   // CG tiling is so much more efficient
01065 #else
01066   const PRUint32 kTilingCopyThreshold = 64;
01067 #endif
01068   if (tiledCols * tiledRows < kTilingCopyThreshold)
01069     return SlowTile(aContext, aSurface, aSXOffset, aSYOffset, 0, 0, aTileRect);
01070   
01071 #ifdef USE_CGPATTERN_TILING
01072   rv = DrawTileWithQuartz(aSurface, aSXOffset, aSYOffset, aPadX, aPadY, aTileRect);
01073 #else
01074   // use the manual methods of tiling
01075   if (!aPadX && !aPadY)
01076     rv = DrawTileQuickly(aContext, aSurface, aSXOffset, aSYOffset, aTileRect);
01077   else
01078     rv = SlowTile(aContext, aSurface, aSXOffset, aSYOffset, aPadX, aPadY, aTileRect);
01079 #endif /* USE_CGPATTERN_TILING */
01080 
01081   return rv;
01082 }