1/* 2 * Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) 3 * Copyright (C) 2004, 2005, 2006 Apple Inc. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "config.h" 28#include "Image.h" 29 30#include "AffineTransform.h" 31#include "BitmapImage.h" 32#include "GraphicsContext.h" 33#include "ImageObserver.h" 34#include "Length.h" 35#include "MIMETypeRegistry.h" 36#include "SharedBuffer.h" 37#include <math.h> 38#include <wtf/MainThread.h> 39#include <wtf/StdLibExtras.h> 40 41#if USE(CG) 42#include <CoreFoundation/CoreFoundation.h> 43#endif 44 45namespace WebCore { 46 47Image::Image(ImageObserver* observer) 48 : m_imageObserver(observer) 49{ 50} 51 52Image::~Image() 53{ 54} 55 56Image* Image::nullImage() 57{ 58 ASSERT(isMainThread()); 59 static Image* nullImage = BitmapImage::create().leakRef(); 60 return nullImage; 61} 62 63bool Image::supportsType(const String& type) 64{ 65 return MIMETypeRegistry::isSupportedImageResourceMIMEType(type); 66} 67 68bool Image::setData(PassRefPtr<SharedBuffer> data, bool allDataReceived) 69{ 70 m_encodedImageData = data; 71 if (!m_encodedImageData.get()) 72 return true; 73 74 int length = m_encodedImageData->size(); 75 if (!length) 76 return true; 77 78 return dataChanged(allDataReceived); 79} 80 81void Image::fillWithSolidColor(GraphicsContext* ctxt, const FloatRect& dstRect, const Color& color, ColorSpace styleColorSpace, CompositeOperator op) 82{ 83 if (!color.alpha()) 84 return; 85 86 CompositeOperator previousOperator = ctxt->compositeOperation(); 87 ctxt->setCompositeOperation(!color.hasAlpha() && op == CompositeSourceOver ? CompositeCopy : op); 88 ctxt->fillRect(dstRect, color, styleColorSpace); 89 ctxt->setCompositeOperation(previousOperator); 90} 91 92void Image::draw(GraphicsContext* ctx, const FloatRect& dstRect, const FloatRect& srcRect, ColorSpace styleColorSpace, CompositeOperator op, BlendMode blendMode, ImageOrientationDescription description) 93{ 94 draw(ctx, dstRect, srcRect, styleColorSpace, op, blendMode, description); 95} 96 97void Image::drawTiled(GraphicsContext* ctxt, const FloatRect& destRect, const FloatPoint& srcPoint, const FloatSize& scaledTileSize, ColorSpace styleColorSpace, CompositeOperator op, BlendMode blendMode) 98{ 99 if (mayFillWithSolidColor()) { 100 fillWithSolidColor(ctxt, destRect, solidColor(), styleColorSpace, op); 101 return; 102 } 103 104 ASSERT(!isBitmapImage() || notSolidColor()); 105 106#if PLATFORM(IOS) 107 FloatSize intrinsicTileSize = originalSize(); 108#else 109 FloatSize intrinsicTileSize = size(); 110#endif 111 if (hasRelativeWidth()) 112 intrinsicTileSize.setWidth(scaledTileSize.width()); 113 if (hasRelativeHeight()) 114 intrinsicTileSize.setHeight(scaledTileSize.height()); 115 116 FloatSize scale(scaledTileSize.width() / intrinsicTileSize.width(), 117 scaledTileSize.height() / intrinsicTileSize.height()); 118 119 FloatRect oneTileRect; 120 FloatSize actualTileSize(scaledTileSize.width() + spaceSize().width(), scaledTileSize.height() + spaceSize().height()); 121 oneTileRect.setX(destRect.x() + fmodf(fmodf(-srcPoint.x(), actualTileSize.width()) - actualTileSize.width(), actualTileSize.width())); 122 oneTileRect.setY(destRect.y() + fmodf(fmodf(-srcPoint.y(), actualTileSize.height()) - actualTileSize.height(), actualTileSize.height())); 123 oneTileRect.setSize(scaledTileSize); 124 125 // Check and see if a single draw of the image can cover the entire area we are supposed to tile. 126 if (oneTileRect.contains(destRect) && !ctxt->drawLuminanceMask()) { 127 FloatRect visibleSrcRect; 128 visibleSrcRect.setX((destRect.x() - oneTileRect.x()) / scale.width()); 129 visibleSrcRect.setY((destRect.y() - oneTileRect.y()) / scale.height()); 130 visibleSrcRect.setWidth(destRect.width() / scale.width()); 131 visibleSrcRect.setHeight(destRect.height() / scale.height()); 132 draw(ctxt, destRect, visibleSrcRect, styleColorSpace, op, blendMode, ImageOrientationDescription()); 133 return; 134 } 135 136#if PLATFORM(IOS) 137 // When using accelerated drawing on iOS, it's faster to stretch an image than to tile it. 138 if (ctxt->isAcceleratedContext()) { 139 if (size().width() == 1 && intersection(oneTileRect, destRect).height() == destRect.height()) { 140 FloatRect visibleSrcRect; 141 visibleSrcRect.setX(0); 142 visibleSrcRect.setY((destRect.y() - oneTileRect.y()) / scale.height()); 143 visibleSrcRect.setWidth(1); 144 visibleSrcRect.setHeight(destRect.height() / scale.height()); 145 draw(ctxt, destRect, visibleSrcRect, styleColorSpace, op, BlendModeNormal, ImageOrientationDescription()); 146 return; 147 } 148 if (size().height() == 1 && intersection(oneTileRect, destRect).width() == destRect.width()) { 149 FloatRect visibleSrcRect; 150 visibleSrcRect.setX((destRect.x() - oneTileRect.x()) / scale.width()); 151 visibleSrcRect.setY(0); 152 visibleSrcRect.setWidth(destRect.width() / scale.width()); 153 visibleSrcRect.setHeight(1); 154 draw(ctxt, destRect, visibleSrcRect, styleColorSpace, op, BlendModeNormal, ImageOrientationDescription()); 155 return; 156 } 157 } 158#endif 159 160 // Patterned images and gradients can use lots of memory for caching when the 161 // tile size is large (<rdar://problem/4691859>, <rdar://problem/6239505>). 162 // Memory consumption depends on the transformed tile size which can get 163 // larger than the original tile if user zooms in enough. 164#if PLATFORM(IOS) 165 const float maxPatternTilePixels = 512 * 512; 166#else 167 const float maxPatternTilePixels = 2048 * 2048; 168#endif 169 FloatRect transformedTileSize = ctxt->getCTM().mapRect(FloatRect(FloatPoint(), scaledTileSize)); 170 float transformedTileSizePixels = transformedTileSize.width() * transformedTileSize.height(); 171 FloatRect currentTileRect = oneTileRect; 172 if (transformedTileSizePixels > maxPatternTilePixels) { 173 GraphicsContextStateSaver stateSaver(*ctxt); 174 ctxt->clip(destRect); 175 176 currentTileRect.shiftYEdgeTo(destRect.y()); 177 float toY = currentTileRect.y(); 178 while (toY < destRect.maxY()) { 179 currentTileRect.shiftXEdgeTo(destRect.x()); 180 float toX = currentTileRect.x(); 181 while (toX < destRect.maxX()) { 182 FloatRect toRect(toX, toY, currentTileRect.width(), currentTileRect.height()); 183 FloatRect fromRect(toFloatPoint(currentTileRect.location() - oneTileRect.location()), currentTileRect.size()); 184 fromRect.scale(1 / scale.width(), 1 / scale.height()); 185 186 draw(ctxt, toRect, fromRect, styleColorSpace, op, BlendModeNormal, ImageOrientationDescription()); 187 toX += currentTileRect.width(); 188 currentTileRect.shiftXEdgeTo(oneTileRect.x()); 189 } 190 toY += currentTileRect.height(); 191 currentTileRect.shiftYEdgeTo(oneTileRect.y()); 192 } 193 return; 194 } 195 196 AffineTransform patternTransform = AffineTransform().scaleNonUniform(scale.width(), scale.height()); 197 FloatRect tileRect(FloatPoint(), intrinsicTileSize); 198 drawPattern(ctxt, tileRect, patternTransform, oneTileRect.location(), styleColorSpace, op, destRect, blendMode); 199 200#if PLATFORM(IOS) 201 startAnimation(DoNotCatchUp); 202#else 203 startAnimation(); 204#endif 205} 206 207// FIXME: Merge with the other drawTiled eventually, since we need a combination of both for some things. 208void Image::drawTiled(GraphicsContext* ctxt, const FloatRect& dstRect, const FloatRect& srcRect, 209 const FloatSize& tileScaleFactor, TileRule hRule, TileRule vRule, ColorSpace styleColorSpace, CompositeOperator op) 210{ 211 if (mayFillWithSolidColor()) { 212 fillWithSolidColor(ctxt, dstRect, solidColor(), styleColorSpace, op); 213 return; 214 } 215 216 // FIXME: We do not support 'round' or 'space' yet. For now just map them to 'repeat'. 217 if (hRule == RoundTile || hRule == SpaceTile) 218 hRule = RepeatTile; 219 if (vRule == RoundTile || vRule == SpaceTile) 220 vRule = RepeatTile; 221 222 AffineTransform patternTransform = AffineTransform().scaleNonUniform(tileScaleFactor.width(), tileScaleFactor.height()); 223 224 // We want to construct the phase such that the pattern is centered (when stretch is not 225 // set for a particular rule). 226 float hPhase = tileScaleFactor.width() * srcRect.x(); 227 float vPhase = tileScaleFactor.height() * srcRect.y(); 228 float scaledTileWidth = tileScaleFactor.width() * srcRect.width(); 229 float scaledTileHeight = tileScaleFactor.height() * srcRect.height(); 230 if (hRule == Image::RepeatTile) 231 hPhase -= (dstRect.width() - scaledTileWidth) / 2; 232 if (vRule == Image::RepeatTile) 233 vPhase -= (dstRect.height() - scaledTileHeight) / 2; 234 FloatPoint patternPhase(dstRect.x() - hPhase, dstRect.y() - vPhase); 235 236 drawPattern(ctxt, srcRect, patternTransform, patternPhase, styleColorSpace, op, dstRect); 237 238#if PLATFORM(IOS) 239 startAnimation(DoNotCatchUp); 240#else 241 startAnimation(); 242#endif 243} 244 245#if ENABLE(IMAGE_DECODER_DOWN_SAMPLING) 246FloatRect Image::adjustSourceRectForDownSampling(const FloatRect& srcRect, const IntSize& scaledSize) const 247{ 248 const FloatSize unscaledSize = size(); 249 if (unscaledSize == scaledSize) 250 return srcRect; 251 252 // Image has been down-sampled. 253 float xscale = static_cast<float>(scaledSize.width()) / unscaledSize.width(); 254 float yscale = static_cast<float>(scaledSize.height()) / unscaledSize.height(); 255 FloatRect scaledSrcRect = srcRect; 256 scaledSrcRect.scale(xscale, yscale); 257 258 return scaledSrcRect; 259} 260#endif 261 262void Image::computeIntrinsicDimensions(Length& intrinsicWidth, Length& intrinsicHeight, FloatSize& intrinsicRatio) 263{ 264#if PLATFORM(IOS) 265 intrinsicRatio = originalSize(); 266#else 267 intrinsicRatio = size(); 268#endif 269 intrinsicWidth = Length(intrinsicRatio.width(), Fixed); 270 intrinsicHeight = Length(intrinsicRatio.height(), Fixed); 271} 272 273} 274