blob: 5c7b56e2d0705f6229ab06d095b339d07adc9cae [file] [log] [blame]
/*
* Copyright (C) 2009 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "platform/graphics/win/TransparencyWin.h"
#include "SkColorPriv.h"
#include "platform/fonts/SimpleFontData.h"
#include "platform/graphics/GraphicsContext.h"
#include "platform/graphics/skia/SkiaUtils.h"
#include "skia/ext/platform_canvas.h"
namespace WebCore {
namespace {
// The maximum size in pixels of the buffer we'll keep around for drawing text
// into. Buffers larger than this will be destroyed when we're done with them.
const int maxCachedBufferPixelSize = 65536;
inline const SkBitmap& bitmapForContext(const GraphicsContext& context)
{
return context.layerBitmap();
}
void compositeToCopy(GraphicsContext& sourceLayers, GraphicsContext& destContext, const AffineTransform& matrix)
{
// Make a list of all devices. The iterator goes top-down, and we want
// bottom-up. Note that each layer can also have an offset in canvas
// coordinates, which is the (x, y) position.
struct DeviceInfo {
DeviceInfo(SkBaseDevice* d, int lx, int ly)
: device(d)
, x(lx)
, y(ly) { }
SkBaseDevice* device;
int x;
int y;
};
Vector<DeviceInfo> devices;
SkCanvas* sourceCanvas = sourceLayers.canvas();
SkCanvas::LayerIter iter(sourceCanvas, false);
while (!iter.done()) {
devices.append(DeviceInfo(iter.device(), iter.x(), iter.y()));
iter.next();
}
// Create a temporary canvas for the compositing into the destination.
SkBitmap* destBmp = const_cast<SkBitmap*>(&bitmapForContext(destContext));
SkCanvas destCanvas(*destBmp);
destCanvas.setMatrix(affineTransformToSkMatrix(matrix));
for (int i = devices.size() - 1; i >= 0; i--) {
const SkBitmap& srcBmp = devices[i].device->accessBitmap(false);
SkRect destRect;
destRect.fLeft = devices[i].x;
destRect.fTop = devices[i].y;
destRect.fRight = destRect.fLeft + srcBmp.width();
destRect.fBottom = destRect.fTop + srcBmp.height();
destCanvas.drawBitmapRect(srcBmp, 0, destRect);
}
}
} // namespace
// If either of these pointers is non-null, both must be valid and point to
// bitmaps of the same size.
class TransparencyWin::OwnedBuffers {
public:
OwnedBuffers(const IntSize& size, bool needReferenceBuffer)
{
m_destBitmap = ImageBuffer::create(size);
if (needReferenceBuffer) {
m_referenceBitmap.setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height());
m_referenceBitmap.allocPixels();
m_referenceBitmap.eraseARGB(0, 0, 0, 0);
}
}
ImageBuffer* destBitmap() { return m_destBitmap.get(); }
// This bitmap will be empty if you don't specify needReferenceBuffer to the
// constructor.
SkBitmap* referenceBitmap() { return &m_referenceBitmap; }
// Returns whether the current layer will fix a buffer of the given size.
bool canHandleSize(const IntSize& size) const
{
return m_destBitmap->size().width() >= size.width() && m_destBitmap->size().height() >= size.height();
}
private:
// The destination bitmap we're drawing into.
OwnPtr<ImageBuffer> m_destBitmap;
// This could be an ImageBuffer but this is an optimization. Since this is
// only ever used as a reference, we don't need to make a full
// PlatformCanvas using Skia on Windows. Just allocating a regular SkBitmap
// is much faster since it's just a Malloc rather than a GDI call.
SkBitmap m_referenceBitmap;
};
TransparencyWin::OwnedBuffers* TransparencyWin::m_cachedBuffers = 0;
TransparencyWin::TransparencyWin()
: m_destContext(0)
, m_orgTransform()
, m_layerMode(NoLayer)
, m_transformMode(KeepTransform)
, m_drawContext(0)
, m_savedOnDrawContext(false)
, m_layerBuffer(0)
, m_referenceBitmap(0)
, m_validLayer(false)
{
}
TransparencyWin::~TransparencyWin()
{
// This should be false, since calling composite() is mandatory.
ASSERT(!m_savedOnDrawContext);
}
void TransparencyWin::composite()
{
// Matches the save() in initializeNewTextContext (or the constructor for
// SCALE) to put the context back into the same state we found it.
if (m_savedOnDrawContext) {
m_drawContext->restore();
m_savedOnDrawContext = false;
}
switch (m_layerMode) {
case NoLayer:
break;
case OpaqueCompositeLayer:
case WhiteLayer:
compositeOpaqueComposite();
break;
case TextComposite:
compositeTextComposite();
break;
}
}
void TransparencyWin::init(GraphicsContext* dest, LayerMode layerMode, TransformMode transformMode, const IntRect& region)
{
m_destContext = dest;
m_orgTransform = dest->getCTM();
m_layerMode = layerMode;
m_transformMode = transformMode;
m_sourceRect = region;
computeLayerSize();
setupLayer();
setupTransform(region);
}
void TransparencyWin::computeLayerSize()
{
if (m_transformMode == Untransform) {
// The meaning of the "transformed" source rect is a little ambigous
// here. The rest of the code doesn't care about it in the Untransform
// case since we're using our own happy coordinate system. So we set it
// to be the source rect since that matches how the code below actually
// uses the variable: to determine how to translate things to account
// for the offset of the layer.
m_transformedSourceRect = m_sourceRect;
} else if (m_transformMode == KeepTransform && m_layerMode != TextComposite) {
// FIXME: support clipping for other modes
IntRect clippedSourceRect = m_sourceRect;
SkRect clipBounds;
if (m_destContext->getClipBounds(&clipBounds)) {
FloatRect clipRect(clipBounds.left(), clipBounds.top(), clipBounds.width(), clipBounds.height());
clippedSourceRect.intersect(enclosingIntRect(clipRect));
}
m_transformedSourceRect = m_orgTransform.mapRect(clippedSourceRect);
} else {
m_transformedSourceRect = m_orgTransform.mapRect(m_sourceRect);
}
m_layerSize = IntSize(m_transformedSourceRect.width(), m_transformedSourceRect.height());
}
void TransparencyWin::setupLayer()
{
switch (m_layerMode) {
case NoLayer:
setupLayerForNoLayer();
break;
case OpaqueCompositeLayer:
setupLayerForOpaqueCompositeLayer();
break;
case TextComposite:
setupLayerForTextComposite();
break;
case WhiteLayer:
setupLayerForWhiteLayer();
break;
}
}
void TransparencyWin::setupLayerForNoLayer()
{
m_drawContext = m_destContext; // Draw to the source context.
m_validLayer = true;
}
void TransparencyWin::setupLayerForOpaqueCompositeLayer()
{
initializeNewContext();
if (!m_validLayer)
return;
AffineTransform mapping;
mapping.translate(-m_transformedSourceRect.x(), -m_transformedSourceRect.y());
if (m_transformMode == Untransform) {
// Compute the inverse mapping from the canvas space to the
// coordinate space of our bitmap.
mapping *= m_orgTransform.inverse();
}
compositeToCopy(*m_destContext, *m_drawContext, mapping);
// Save the reference layer so we can tell what changed.
SkCanvas referenceCanvas(*m_referenceBitmap);
referenceCanvas.drawBitmap(bitmapForContext(*m_drawContext), 0, 0);
// Layer rect represents the part of the original layer.
}
void TransparencyWin::setupLayerForTextComposite()
{
ASSERT(m_transformMode == KeepTransform);
// Fall through to filling with white.
setupLayerForWhiteLayer();
}
void TransparencyWin::setupLayerForWhiteLayer()
{
initializeNewContext();
if (!m_validLayer)
return;
m_drawContext->fillRect(IntRect(IntPoint(0, 0), m_layerSize), Color::white);
// Layer rect represents the part of the original layer.
}
void TransparencyWin::setupTransform(const IntRect& region)
{
switch (m_transformMode) {
case KeepTransform:
setupTransformForKeepTransform(region);
break;
case Untransform:
setupTransformForUntransform();
break;
case ScaleTransform:
setupTransformForScaleTransform();
break;
}
}
void TransparencyWin::setupTransformForKeepTransform(const IntRect& region)
{
if (!m_validLayer)
return;
if (m_layerMode != NoLayer) {
// Need to save things since we're modifying the transform.
m_drawContext->save();
m_savedOnDrawContext = true;
// Account for the fact that the layer may be offset from the
// original. This only happens when we create a layer that has the
// same coordinate space as the parent.
AffineTransform xform;
xform.translate(-m_transformedSourceRect.x(), -m_transformedSourceRect.y());
// We're making a layer, so apply the old transform to the new one
// so it's maintained. We know the new layer has the identity
// transform now, we we can just multiply it.
xform *= m_orgTransform;
m_drawContext->concatCTM(xform);
}
m_drawRect = m_sourceRect;
}
void TransparencyWin::setupTransformForUntransform()
{
ASSERT(m_layerMode != NoLayer);
// We now have a new layer with the identity transform, which is the
// Untransformed space we'll use for drawing.
m_drawRect = IntRect(IntPoint(0, 0), m_layerSize);
}
void TransparencyWin::setupTransformForScaleTransform()
{
if (!m_validLayer)
return;
if (m_layerMode == NoLayer) {
// Need to save things since we're modifying the layer.
m_drawContext->save();
m_savedOnDrawContext = true;
// Undo the transform on the current layer when we're re-using the
// current one.
m_drawContext->concatCTM(m_drawContext->getCTM().inverse());
// We're drawing to the original layer with just a different size.
m_drawRect = m_transformedSourceRect;
} else {
// Just go ahead and use the layer's coordinate space to draw into.
// It will have the scaled size, and an identity transform loaded.
m_drawRect = IntRect(IntPoint(0, 0), m_layerSize);
}
}
void TransparencyWin::setTextCompositeColor(Color color)
{
m_textCompositeColor = color;
}
void TransparencyWin::initializeNewContext()
{
int pixelSize = m_layerSize.width() * m_layerSize.height();
if (pixelSize <= 0)
return;
if (pixelSize > maxCachedBufferPixelSize) {
// Create a 1-off buffer for drawing into. We only need the reference
// buffer if we're making an OpaqueCompositeLayer.
bool needReferenceBitmap = m_layerMode == OpaqueCompositeLayer;
m_ownedBuffers = adoptPtr(new OwnedBuffers(m_layerSize, needReferenceBitmap));
m_layerBuffer = m_ownedBuffers->destBitmap();
if (!m_layerBuffer)
return;
m_drawContext = m_layerBuffer->context();
if (needReferenceBitmap) {
m_referenceBitmap = m_ownedBuffers->referenceBitmap();
if (!m_referenceBitmap || !m_referenceBitmap->getPixels())
return;
}
m_validLayer = true;
return;
}
if (m_cachedBuffers && m_cachedBuffers->canHandleSize(m_layerSize)) {
// We can re-use the existing buffer. We don't need to clear it since
// all layer modes will clear it in their initialization.
m_layerBuffer = m_cachedBuffers->destBitmap();
m_drawContext = m_cachedBuffers->destBitmap()->context();
bitmapForContext(*m_drawContext).eraseARGB(0, 0, 0, 0);
m_referenceBitmap = m_cachedBuffers->referenceBitmap();
m_referenceBitmap->eraseARGB(0, 0, 0, 0);
m_validLayer = true;
return;
}
// Create a new cached buffer.
if (m_cachedBuffers)
delete m_cachedBuffers;
m_cachedBuffers = new OwnedBuffers(m_layerSize, true);
m_layerBuffer = m_cachedBuffers->destBitmap();
m_drawContext = m_cachedBuffers->destBitmap()->context();
m_referenceBitmap = m_cachedBuffers->referenceBitmap();
m_validLayer = true;
}
void TransparencyWin::compositeOpaqueComposite()
{
if (!m_validLayer)
return;
m_destContext->save();
SkBitmap* bitmap = const_cast<SkBitmap*>(
&bitmapForContext(*m_layerBuffer->context()));
// This function will be called for WhiteLayer as well, which we don't want
// to change.
if (m_layerMode == OpaqueCompositeLayer) {
// Fix up our bitmap, making it contain only the pixels which changed
// and transparent everywhere else.
SkAutoLockPixels sourceLock(*m_referenceBitmap);
SkAutoLockPixels lock(*bitmap);
for (int y = 0; y < bitmap->height(); y++) {
uint32_t* source = m_referenceBitmap->getAddr32(0, y);
uint32_t* dest = bitmap->getAddr32(0, y);
for (int x = 0; x < bitmap->width(); x++) {
// Clear out any pixels that were untouched.
if (dest[x] == source[x])
dest[x] = 0;
else
dest[x] |= (0xFF << SK_A32_SHIFT);
}
}
} else {
makeLayerOpaque();
}
SkRect destRect;
if (m_transformMode != Untransform) {
// We want to use Untransformed space.
//
// Note that we DON'T call m_layerBuffer->image() here. This actually
// makes a copy of the image, which is unnecessary and slow. Instead, we
// just draw the image from inside the destination context.
SkMatrix identity;
identity.reset();
m_destContext->setMatrix(identity);
destRect.set(m_transformedSourceRect.x(), m_transformedSourceRect.y(), m_transformedSourceRect.maxX(), m_transformedSourceRect.maxY());
} else {
destRect.set(m_sourceRect.x(), m_sourceRect.y(), m_sourceRect.maxX(), m_sourceRect.maxY());
}
SkPaint paint;
paint.setFilterBitmap(true);
paint.setAntiAlias(true);
// Note that we need to specify the source layer subset, since the bitmap
// may have been cached and it could be larger than what we're using.
SkRect sourceRect = SkRect::MakeWH(
SkIntToScalar(m_layerSize.width()),
SkIntToScalar(m_layerSize.height()));
m_destContext->drawBitmapRect(*bitmap, &sourceRect, destRect, &paint);
m_destContext->restore();
}
void TransparencyWin::compositeTextComposite()
{
if (!m_validLayer)
return;
const SkBitmap& bitmap = m_layerBuffer->context()->layerBitmap(GraphicsContext::ReadWrite);
SkColor textColor = m_textCompositeColor.rgb();
for (int y = 0; y < m_layerSize.height(); y++) {
uint32_t* row = bitmap.getAddr32(0, y);
for (int x = 0; x < m_layerSize.width(); x++) {
// The alpha is the average of the R, G, and B channels.
int alpha = (SkGetPackedR32(row[x]) + SkGetPackedG32(row[x]) + SkGetPackedB32(row[x])) / 3;
// Apply that alpha to the text color and write the result.
row[x] = SkAlphaMulQ(textColor, SkAlpha255To256(255 - alpha));
}
}
// Now the layer has text with the proper color and opacity.
m_destContext->save();
// We want to use Untransformed space (see above)
SkMatrix identity;
identity.reset();
m_destContext->setMatrix(identity);
SkRect destRect = { m_transformedSourceRect.x(), m_transformedSourceRect.y(), m_transformedSourceRect.maxX(), m_transformedSourceRect.maxY() };
// Note that we need to specify the source layer subset, since the bitmap
// may have been cached and it could be larger than what we're using.
SkRect sourceRect = SkRect::MakeWH(
SkIntToScalar(m_layerSize.width()),
SkIntToScalar(m_layerSize.height()));
m_destContext->drawBitmapRect(bitmap, &sourceRect, destRect, 0);
m_destContext->restore();
}
void TransparencyWin::makeLayerOpaque()
{
if (!m_validLayer)
return;
SkBitmap& bitmap = const_cast<SkBitmap&>(m_drawContext->layerBitmap(GraphicsContext::ReadWrite));
for (int y = 0; y < m_layerSize.height(); y++) {
uint32_t* row = bitmap.getAddr32(0, y);
for (int x = 0; x < m_layerSize.width(); x++)
row[x] |= 0xFF000000;
}
}
} // namespace WebCore