michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: michael@0: #include "mozilla/MemoryReporting.h" michael@0: #if defined(HAVE_POSIX_MEMALIGN) michael@0: #include "gfxAlphaRecovery.h" michael@0: #endif michael@0: #include "gfxImageSurface.h" michael@0: michael@0: #include "cairo.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: #include "gfx2DGlue.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::gfx; michael@0: michael@0: gfxImageSurface::gfxImageSurface() michael@0: : mSize(0, 0), michael@0: mOwnsData(false), michael@0: mFormat(gfxImageFormat::Unknown), michael@0: mStride(0) michael@0: { michael@0: } michael@0: michael@0: void michael@0: gfxImageSurface::InitFromSurface(cairo_surface_t *csurf) michael@0: { michael@0: mSize.width = cairo_image_surface_get_width(csurf); michael@0: mSize.height = cairo_image_surface_get_height(csurf); michael@0: mData = cairo_image_surface_get_data(csurf); michael@0: mFormat = (gfxImageFormat) cairo_image_surface_get_format(csurf); michael@0: mOwnsData = false; michael@0: mStride = cairo_image_surface_get_stride(csurf); michael@0: michael@0: Init(csurf, true); michael@0: } michael@0: michael@0: gfxImageSurface::gfxImageSurface(unsigned char *aData, const gfxIntSize& aSize, michael@0: long aStride, gfxImageFormat aFormat) michael@0: { michael@0: InitWithData(aData, aSize, aStride, aFormat); michael@0: } michael@0: michael@0: void michael@0: gfxImageSurface::MakeInvalid() michael@0: { michael@0: mSize = gfxIntSize(-1, -1); michael@0: mData = nullptr; michael@0: mStride = 0; michael@0: } michael@0: michael@0: void michael@0: gfxImageSurface::InitWithData(unsigned char *aData, const gfxIntSize& aSize, michael@0: long aStride, gfxImageFormat aFormat) michael@0: { michael@0: mSize = aSize; michael@0: mOwnsData = false; michael@0: mData = aData; michael@0: mFormat = aFormat; michael@0: mStride = aStride; michael@0: michael@0: if (!CheckSurfaceSize(aSize)) michael@0: MakeInvalid(); michael@0: michael@0: cairo_surface_t *surface = michael@0: cairo_image_surface_create_for_data((unsigned char*)mData, michael@0: (cairo_format_t)(int)mFormat, michael@0: mSize.width, michael@0: mSize.height, michael@0: mStride); michael@0: michael@0: // cairo_image_surface_create_for_data can return a 'null' surface michael@0: // in out of memory conditions. The gfxASurface::Init call checks michael@0: // the surface it receives to see if there is an error with the michael@0: // surface and handles it appropriately. That is why there is michael@0: // no check here. michael@0: Init(surface); michael@0: } michael@0: michael@0: static void* michael@0: TryAllocAlignedBytes(size_t aSize) michael@0: { michael@0: // Use fallible allocators here michael@0: #if defined(HAVE_POSIX_MEMALIGN) michael@0: void* ptr; michael@0: // Try to align for fast alpha recovery. This should only help michael@0: // cairo too, can't hurt. michael@0: return moz_posix_memalign(&ptr, michael@0: 1 << gfxAlphaRecovery::GoodAlignmentLog2(), michael@0: aSize) ? michael@0: nullptr : ptr; michael@0: #else michael@0: // Oh well, hope that luck is with us in the allocator michael@0: return moz_malloc(aSize); michael@0: #endif michael@0: } michael@0: michael@0: gfxImageSurface::gfxImageSurface(const gfxIntSize& size, gfxImageFormat format, bool aClear) michael@0: : mSize(size), mData(nullptr), mFormat(format) michael@0: { michael@0: AllocateAndInit(0, 0, aClear); michael@0: } michael@0: michael@0: void michael@0: gfxImageSurface::AllocateAndInit(long aStride, int32_t aMinimalAllocation, michael@0: bool aClear) michael@0: { michael@0: // The callers should set mSize and mFormat. michael@0: MOZ_ASSERT(!mData); michael@0: mData = nullptr; michael@0: mOwnsData = false; michael@0: michael@0: mStride = aStride > 0 ? aStride : ComputeStride(); michael@0: if (aMinimalAllocation < mSize.height * mStride) michael@0: aMinimalAllocation = mSize.height * mStride; michael@0: michael@0: if (!CheckSurfaceSize(mSize)) michael@0: MakeInvalid(); michael@0: michael@0: // if we have a zero-sized surface, just leave mData nullptr michael@0: if (mSize.height * mStride > 0) { michael@0: michael@0: // This can fail to allocate memory aligned as we requested, michael@0: // or it can fail to allocate any memory at all. michael@0: mData = (unsigned char *) TryAllocAlignedBytes(aMinimalAllocation); michael@0: if (!mData) michael@0: return; michael@0: if (aClear) michael@0: memset(mData, 0, aMinimalAllocation); michael@0: } michael@0: michael@0: mOwnsData = true; michael@0: michael@0: cairo_surface_t *surface = michael@0: cairo_image_surface_create_for_data((unsigned char*)mData, michael@0: (cairo_format_t)(int)mFormat, michael@0: mSize.width, michael@0: mSize.height, michael@0: mStride); michael@0: michael@0: Init(surface); michael@0: michael@0: if (mSurfaceValid) { michael@0: RecordMemoryUsed(mSize.height * ComputeStride() + michael@0: sizeof(gfxImageSurface)); michael@0: } michael@0: } michael@0: michael@0: gfxImageSurface::gfxImageSurface(const gfxIntSize& size, gfxImageFormat format, michael@0: long aStride, int32_t aExtraBytes, bool aClear) michael@0: : mSize(size), mData(nullptr), mFormat(format) michael@0: { michael@0: AllocateAndInit(aStride, aExtraBytes, aClear); michael@0: } michael@0: michael@0: gfxImageSurface::gfxImageSurface(cairo_surface_t *csurf) michael@0: { michael@0: mSize.width = cairo_image_surface_get_width(csurf); michael@0: mSize.height = cairo_image_surface_get_height(csurf); michael@0: mData = cairo_image_surface_get_data(csurf); michael@0: mFormat = (gfxImageFormat) cairo_image_surface_get_format(csurf); michael@0: mOwnsData = false; michael@0: mStride = cairo_image_surface_get_stride(csurf); michael@0: michael@0: Init(csurf, true); michael@0: } michael@0: michael@0: gfxImageSurface::~gfxImageSurface() michael@0: { michael@0: if (mOwnsData) michael@0: free(mData); michael@0: } michael@0: michael@0: /*static*/ long michael@0: gfxImageSurface::ComputeStride(const gfxIntSize& aSize, gfxImageFormat aFormat) michael@0: { michael@0: long stride; michael@0: michael@0: if (aFormat == gfxImageFormat::ARGB32) michael@0: stride = aSize.width * 4; michael@0: else if (aFormat == gfxImageFormat::RGB24) michael@0: stride = aSize.width * 4; michael@0: else if (aFormat == gfxImageFormat::RGB16_565) michael@0: stride = aSize.width * 2; michael@0: else if (aFormat == gfxImageFormat::A8) michael@0: stride = aSize.width; michael@0: else if (aFormat == gfxImageFormat::A1) { michael@0: stride = (aSize.width + 7) / 8; michael@0: } else { michael@0: NS_WARNING("Unknown format specified to gfxImageSurface!"); michael@0: stride = aSize.width * 4; michael@0: } michael@0: michael@0: stride = ((stride + 3) / 4) * 4; michael@0: michael@0: return stride; michael@0: } michael@0: michael@0: size_t michael@0: gfxImageSurface::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t n = gfxASurface::SizeOfExcludingThis(aMallocSizeOf); michael@0: if (mOwnsData) { michael@0: n += aMallocSizeOf(mData); michael@0: } michael@0: return n; michael@0: } michael@0: michael@0: size_t michael@0: gfxImageSurface::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: bool michael@0: gfxImageSurface::SizeOfIsMeasured() const michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: // helper function for the CopyFrom methods michael@0: static void michael@0: CopyForStride(unsigned char* aDest, unsigned char* aSrc, const gfxIntSize& aSize, long aDestStride, long aSrcStride) michael@0: { michael@0: if (aDestStride == aSrcStride) { michael@0: memcpy (aDest, aSrc, aSrcStride * aSize.height); michael@0: } else { michael@0: int lineSize = std::min(aDestStride, aSrcStride); michael@0: for (int i = 0; i < aSize.height; i++) { michael@0: unsigned char* src = aSrc + aSrcStride * i; michael@0: unsigned char* dst = aDest + aDestStride * i; michael@0: michael@0: memcpy (dst, src, lineSize); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // helper function for the CopyFrom methods michael@0: static bool michael@0: FormatsAreCompatible(gfxImageFormat a1, gfxImageFormat a2) michael@0: { michael@0: if (a1 != a2 && michael@0: !(a1 == gfxImageFormat::ARGB32 && michael@0: a2 == gfxImageFormat::RGB24) && michael@0: !(a1 == gfxImageFormat::RGB24 && michael@0: a2 == gfxImageFormat::ARGB32)) { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: gfxImageSurface::CopyFrom (SourceSurface *aSurface) michael@0: { michael@0: mozilla::RefPtr data = aSurface->GetDataSurface(); michael@0: michael@0: if (!data) { michael@0: return false; michael@0: } michael@0: michael@0: gfxIntSize size(data->GetSize().width, data->GetSize().height); michael@0: if (size != mSize) { michael@0: return false; michael@0: } michael@0: michael@0: if (!FormatsAreCompatible(SurfaceFormatToImageFormat(aSurface->GetFormat()), michael@0: mFormat)) { michael@0: return false; michael@0: } michael@0: michael@0: CopyForStride(mData, data->GetData(), size, mStride, data->Stride()); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: michael@0: bool michael@0: gfxImageSurface::CopyFrom(gfxImageSurface *other) michael@0: { michael@0: if (other->mSize != mSize) { michael@0: return false; michael@0: } michael@0: michael@0: if (!FormatsAreCompatible(other->mFormat, mFormat)) { michael@0: return false; michael@0: } michael@0: michael@0: CopyForStride(mData, other->mData, mSize, mStride, other->mStride); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: gfxImageSurface::CopyTo(SourceSurface *aSurface) { michael@0: mozilla::RefPtr data = aSurface->GetDataSurface(); michael@0: michael@0: if (!data) { michael@0: return false; michael@0: } michael@0: michael@0: gfxIntSize size(data->GetSize().width, data->GetSize().height); michael@0: if (size != mSize) { michael@0: return false; michael@0: } michael@0: michael@0: if (!FormatsAreCompatible(SurfaceFormatToImageFormat(aSurface->GetFormat()), michael@0: mFormat)) { michael@0: return false; michael@0: } michael@0: michael@0: CopyForStride(data->GetData(), mData, size, data->Stride(), mStride); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: TemporaryRef michael@0: gfxImageSurface::CopyToB8G8R8A8DataSourceSurface() michael@0: { michael@0: RefPtr dataSurface = michael@0: Factory::CreateDataSourceSurface(IntSize(GetSize().width, GetSize().height), michael@0: SurfaceFormat::B8G8R8A8); michael@0: if (dataSurface) { michael@0: CopyTo(dataSurface); michael@0: } michael@0: return dataSurface.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: gfxImageSurface::GetSubimage(const gfxRect& aRect) michael@0: { michael@0: gfxRect r(aRect); michael@0: r.Round(); michael@0: MOZ_ASSERT(gfxRect(0, 0, mSize.width, mSize.height).Contains(r)); michael@0: michael@0: gfxImageFormat format = Format(); michael@0: michael@0: unsigned char* subData = Data() + michael@0: (Stride() * (int)r.Y()) + michael@0: (int)r.X() * gfxASurface::BytePerPixelFromFormat(Format()); michael@0: michael@0: if (format == gfxImageFormat::ARGB32 && michael@0: GetOpaqueRect().Contains(aRect)) { michael@0: format = gfxImageFormat::RGB24; michael@0: } michael@0: michael@0: nsRefPtr image = michael@0: new gfxSubimageSurface(this, subData, michael@0: gfxIntSize((int)r.Width(), (int)r.Height()), michael@0: format); michael@0: michael@0: return image.forget(); michael@0: } michael@0: michael@0: gfxSubimageSurface::gfxSubimageSurface(gfxImageSurface* aParent, michael@0: unsigned char* aData, michael@0: const gfxIntSize& aSize, michael@0: gfxImageFormat aFormat) michael@0: : gfxImageSurface(aData, aSize, aParent->Stride(), aFormat) michael@0: , mParent(aParent) michael@0: { michael@0: } michael@0: michael@0: already_AddRefed michael@0: gfxImageSurface::GetAsImageSurface() michael@0: { michael@0: nsRefPtr surface = this; michael@0: return surface.forget(); michael@0: } michael@0: michael@0: void michael@0: gfxImageSurface::MovePixels(const nsIntRect& aSourceRect, michael@0: const nsIntPoint& aDestTopLeft) michael@0: { michael@0: const nsIntRect bounds(0, 0, mSize.width, mSize.height); michael@0: nsIntPoint offset = aDestTopLeft - aSourceRect.TopLeft(); michael@0: nsIntRect clippedSource = aSourceRect; michael@0: clippedSource.IntersectRect(clippedSource, bounds); michael@0: nsIntRect clippedDest = clippedSource + offset; michael@0: clippedDest.IntersectRect(clippedDest, bounds); michael@0: const nsIntRect dest = clippedDest; michael@0: const nsIntRect source = dest - offset; michael@0: // NB: this relies on IntersectRect() and operator+/- preserving michael@0: // x/y for empty rectangles michael@0: NS_ABORT_IF_FALSE(bounds.Contains(dest) && bounds.Contains(source) && michael@0: aSourceRect.Contains(source) && michael@0: nsIntRect(aDestTopLeft, aSourceRect.Size()).Contains(dest) && michael@0: source.Size() == dest.Size() && michael@0: offset == (dest.TopLeft() - source.TopLeft()), michael@0: "Messed up clipping, crash or corruption will follow"); michael@0: if (source.IsEmpty() || source.IsEqualInterior(dest)) { michael@0: return; michael@0: } michael@0: michael@0: long naturalStride = ComputeStride(mSize, mFormat); michael@0: if (mStride == naturalStride && dest.width == bounds.width) { michael@0: // Fast path: this is a vertical shift of some rows in a michael@0: // "normal" image surface. We can directly memmove and michael@0: // hopefully stay in SIMD land. michael@0: unsigned char* dst = mData + dest.y * mStride; michael@0: const unsigned char* src = mData + source.y * mStride; michael@0: size_t nBytes = dest.height * mStride; michael@0: memmove(dst, src, nBytes); michael@0: return; michael@0: } michael@0: michael@0: // Slow(er) path: have to move row-by-row. michael@0: const int32_t bpp = BytePerPixelFromFormat(mFormat); michael@0: const size_t nRowBytes = dest.width * bpp; michael@0: // dstRow points at the first pixel within the current destination michael@0: // row, and similarly for srcRow. endSrcRow is one row beyond the michael@0: // last row we need to copy. stride is either +mStride or michael@0: // -mStride, depending on which direction we're copying. michael@0: unsigned char* dstRow; michael@0: unsigned char* srcRow; michael@0: unsigned char* endSrcRow; // NB: this may point outside the image michael@0: long stride; michael@0: if (dest.y > source.y) { michael@0: // We're copying down from source to dest, so walk backwards michael@0: // starting from the last rows to avoid stomping pixels we michael@0: // need. michael@0: stride = -mStride; michael@0: dstRow = mData + dest.x * bpp + (dest.YMost() - 1) * mStride; michael@0: srcRow = mData + source.x * bpp + (source.YMost() - 1) * mStride; michael@0: endSrcRow = mData + source.x * bpp + (source.y - 1) * mStride; michael@0: } else { michael@0: stride = mStride; michael@0: dstRow = mData + dest.x * bpp + dest.y * mStride; michael@0: srcRow = mData + source.x * bpp + source.y * mStride; michael@0: endSrcRow = mData + source.x * bpp + source.YMost() * mStride; michael@0: } michael@0: michael@0: for (; srcRow != endSrcRow; dstRow += stride, srcRow += stride) { michael@0: memmove(dstRow, srcRow, nRowBytes); michael@0: } michael@0: }