michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- 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: #include "SourceSurfaceCG.h" michael@0: #include "DrawTargetCG.h" michael@0: #include "DataSourceSurfaceWrapper.h" michael@0: #include "DataSurfaceHelpers.h" michael@0: #include "mozilla/Types.h" // for decltype michael@0: michael@0: #include "MacIOSurface.h" michael@0: #include "Tools.h" michael@0: michael@0: namespace mozilla { michael@0: namespace gfx { michael@0: michael@0: michael@0: SourceSurfaceCG::~SourceSurfaceCG() michael@0: { michael@0: CGImageRelease(mImage); michael@0: } michael@0: michael@0: IntSize michael@0: SourceSurfaceCG::GetSize() const michael@0: { michael@0: IntSize size; michael@0: size.width = CGImageGetWidth(mImage); michael@0: size.height = CGImageGetHeight(mImage); michael@0: return size; michael@0: } michael@0: michael@0: SurfaceFormat michael@0: SourceSurfaceCG::GetFormat() const michael@0: { michael@0: return mFormat; michael@0: } michael@0: michael@0: TemporaryRef michael@0: SourceSurfaceCG::GetDataSurface() michael@0: { michael@0: //XXX: we should be more disciplined about who takes a reference and where michael@0: CGImageRetain(mImage); michael@0: RefPtr dataSurf = new DataSourceSurfaceCG(mImage); michael@0: michael@0: // We also need to make sure that the returned surface has michael@0: // surface->GetType() == SurfaceType::DATA. michael@0: dataSurf = new DataSourceSurfaceWrapper(dataSurf); michael@0: michael@0: return dataSurf; michael@0: } michael@0: michael@0: static void releaseCallback(void *info, const void *data, size_t size) { michael@0: free(info); michael@0: } michael@0: michael@0: CGImageRef michael@0: CreateCGImage(void *aInfo, michael@0: const void *aData, michael@0: const IntSize &aSize, michael@0: int32_t aStride, michael@0: SurfaceFormat aFormat) michael@0: { michael@0: //XXX: we should avoid creating this colorspace everytime michael@0: CGColorSpaceRef colorSpace = nullptr; michael@0: CGBitmapInfo bitinfo = 0; michael@0: int bitsPerComponent = 0; michael@0: int bitsPerPixel = 0; michael@0: michael@0: switch (aFormat) { michael@0: case SurfaceFormat::B8G8R8A8: michael@0: colorSpace = CGColorSpaceCreateDeviceRGB(); michael@0: bitinfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; michael@0: bitsPerComponent = 8; michael@0: bitsPerPixel = 32; michael@0: break; michael@0: michael@0: case SurfaceFormat::B8G8R8X8: michael@0: colorSpace = CGColorSpaceCreateDeviceRGB(); michael@0: bitinfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host; michael@0: bitsPerComponent = 8; michael@0: bitsPerPixel = 32; michael@0: break; michael@0: michael@0: case SurfaceFormat::A8: michael@0: // XXX: why don't we set a colorspace here? michael@0: bitsPerComponent = 8; michael@0: bitsPerPixel = 8; michael@0: break; michael@0: michael@0: default: michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: size_t bufLen = BufferSizeFromStrideAndHeight(aStride, aSize.height); michael@0: if (bufLen == 0) { michael@0: return nullptr; michael@0: } michael@0: CGDataProviderRef dataProvider = CGDataProviderCreateWithData(aInfo, michael@0: aData, michael@0: bufLen, michael@0: releaseCallback); michael@0: michael@0: CGImageRef image; michael@0: if (aFormat == SurfaceFormat::A8) { michael@0: CGFloat decode[] = {1.0, 0.0}; michael@0: image = CGImageMaskCreate (aSize.width, aSize.height, michael@0: bitsPerComponent, michael@0: bitsPerPixel, michael@0: aStride, michael@0: dataProvider, michael@0: decode, michael@0: true); michael@0: } else { michael@0: image = CGImageCreate (aSize.width, aSize.height, michael@0: bitsPerComponent, michael@0: bitsPerPixel, michael@0: aStride, michael@0: colorSpace, michael@0: bitinfo, michael@0: dataProvider, michael@0: nullptr, michael@0: true, michael@0: kCGRenderingIntentDefault); michael@0: } michael@0: michael@0: CGDataProviderRelease(dataProvider); michael@0: CGColorSpaceRelease(colorSpace); michael@0: michael@0: return image; michael@0: } michael@0: michael@0: bool michael@0: SourceSurfaceCG::InitFromData(unsigned char *aData, michael@0: const IntSize &aSize, michael@0: int32_t aStride, michael@0: SurfaceFormat aFormat) michael@0: { michael@0: assert(aSize.width >= 0 && aSize.height >= 0); michael@0: michael@0: size_t bufLen = BufferSizeFromStrideAndHeight(aStride, aSize.height); michael@0: if (bufLen == 0) { michael@0: mImage = nullptr; michael@0: return false; michael@0: } michael@0: michael@0: void *data = malloc(bufLen); michael@0: // Copy all the data except the stride padding on the very last michael@0: // row since we can't guarantee that is readable. michael@0: memcpy(data, aData, bufLen - aStride + (aSize.width * BytesPerPixel(aFormat))); michael@0: michael@0: mFormat = aFormat; michael@0: mImage = CreateCGImage(data, data, aSize, aStride, aFormat); michael@0: michael@0: return mImage != nullptr; michael@0: } michael@0: michael@0: DataSourceSurfaceCG::~DataSourceSurfaceCG() michael@0: { michael@0: CGImageRelease(mImage); michael@0: free(CGBitmapContextGetData(mCg)); michael@0: CGContextRelease(mCg); michael@0: } michael@0: michael@0: IntSize michael@0: DataSourceSurfaceCG::GetSize() const michael@0: { michael@0: IntSize size; michael@0: size.width = CGImageGetWidth(mImage); michael@0: size.height = CGImageGetHeight(mImage); michael@0: return size; michael@0: } michael@0: michael@0: bool michael@0: DataSourceSurfaceCG::InitFromData(unsigned char *aData, michael@0: const IntSize &aSize, michael@0: int32_t aStride, michael@0: SurfaceFormat aFormat) michael@0: { michael@0: if (aSize.width <= 0 || aSize.height <= 0) { michael@0: return false; michael@0: } michael@0: michael@0: size_t bufLen = BufferSizeFromStrideAndHeight(aStride, aSize.height); michael@0: if (bufLen == 0) { michael@0: mImage = nullptr; michael@0: return false; michael@0: } michael@0: michael@0: void *data = malloc(bufLen); michael@0: memcpy(data, aData, bufLen - aStride + (aSize.width * BytesPerPixel(aFormat))); michael@0: michael@0: mFormat = aFormat; michael@0: mImage = CreateCGImage(data, data, aSize, aStride, aFormat); michael@0: michael@0: if (!mImage) { michael@0: free(data); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: CGContextRef CreateBitmapContextForImage(CGImageRef image) michael@0: { michael@0: CGColorSpaceRef colorSpace; michael@0: michael@0: size_t width = CGImageGetWidth(image); michael@0: size_t height = CGImageGetHeight(image); michael@0: michael@0: int bitmapBytesPerRow = (width * 4); michael@0: int bitmapByteCount = (bitmapBytesPerRow * height); michael@0: michael@0: void *data = calloc(bitmapByteCount, 1); michael@0: //XXX: which color space should we be using here? michael@0: colorSpace = CGColorSpaceCreateDeviceRGB(); michael@0: assert(colorSpace); michael@0: michael@0: // we'd like to pass nullptr as the first parameter michael@0: // to let Quartz manage this memory for us. However, michael@0: // on 10.5 and older CGBitmapContextGetData will return michael@0: // nullptr instead of the associated buffer so we need michael@0: // to manage it ourselves. michael@0: CGContextRef cg = CGBitmapContextCreate(data, michael@0: width, michael@0: height, michael@0: 8, michael@0: bitmapBytesPerRow, michael@0: colorSpace, michael@0: kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); michael@0: assert(cg); michael@0: michael@0: CGColorSpaceRelease(colorSpace); michael@0: michael@0: return cg; michael@0: } michael@0: michael@0: DataSourceSurfaceCG::DataSourceSurfaceCG(CGImageRef aImage) michael@0: { michael@0: mFormat = SurfaceFormat::B8G8R8A8; michael@0: mImage = aImage; michael@0: mCg = CreateBitmapContextForImage(aImage); michael@0: if (mCg == nullptr) { michael@0: // error creating context michael@0: return; michael@0: } michael@0: michael@0: // Get image width, height. We'll use the entire image. michael@0: CGFloat w = CGImageGetWidth(aImage); michael@0: CGFloat h = CGImageGetHeight(aImage); michael@0: CGRect rect = {{0,0},{w,h}}; michael@0: michael@0: // Draw the image to the bitmap context. Once we draw, the memory michael@0: // allocated for the context for rendering will then contain the michael@0: // raw image data in the specified color space. michael@0: CGContextDrawImage(mCg, rect, aImage); michael@0: michael@0: // Now we can get a pointer to the image data associated with the bitmap michael@0: // context. michael@0: mData = CGBitmapContextGetData(mCg); michael@0: assert(mData); michael@0: } michael@0: michael@0: unsigned char * michael@0: DataSourceSurfaceCG::GetData() michael@0: { michael@0: // See http://developer.apple.com/library/mac/#qa/qa1509/_index.html michael@0: // the following only works on 10.5+, the Q&A above suggests a method michael@0: // that can be used for earlier versions michael@0: //CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(cgImage)); michael@0: //unsigned char *dataPtr = CFDataGetBytePtr(data); michael@0: //CFDataRelease(data); michael@0: // unfortunately the the method above only works for read-only access and michael@0: // we need read-write for DataSourceSurfaces michael@0: return (unsigned char*)mData; michael@0: } michael@0: michael@0: SourceSurfaceCGBitmapContext::SourceSurfaceCGBitmapContext(DrawTargetCG *aDrawTarget) michael@0: { michael@0: mDrawTarget = aDrawTarget; michael@0: mFormat = aDrawTarget->GetFormat(); michael@0: mCg = (CGContextRef)aDrawTarget->GetNativeSurface(NativeSurfaceType::CGCONTEXT); michael@0: if (!mCg) michael@0: abort(); michael@0: michael@0: mSize.width = CGBitmapContextGetWidth(mCg); michael@0: mSize.height = CGBitmapContextGetHeight(mCg); michael@0: mStride = CGBitmapContextGetBytesPerRow(mCg); michael@0: mData = CGBitmapContextGetData(mCg); michael@0: michael@0: mImage = nullptr; michael@0: } michael@0: michael@0: void SourceSurfaceCGBitmapContext::EnsureImage() const michael@0: { michael@0: // Instead of using CGBitmapContextCreateImage we create michael@0: // a CGImage around the data associated with the CGBitmapContext michael@0: // we do this to avoid the vm_copy that CGBitmapContextCreateImage. michael@0: // vm_copy tends to cause all sorts of unexpected performance problems michael@0: // because of the mm tricks that vm_copy does. Using a regular michael@0: // memcpy when the bitmap context is modified gives us more predictable michael@0: // performance characteristics. michael@0: if (!mImage) { michael@0: if (!mData) abort(); michael@0: mImage = CreateCGImage(nullptr, mData, mSize, mStride, mFormat); michael@0: } michael@0: } michael@0: michael@0: IntSize michael@0: SourceSurfaceCGBitmapContext::GetSize() const michael@0: { michael@0: return mSize; michael@0: } michael@0: michael@0: void michael@0: SourceSurfaceCGBitmapContext::DrawTargetWillChange() michael@0: { michael@0: if (mDrawTarget) { michael@0: // This will break the weak reference we hold to mCg michael@0: size_t stride = CGBitmapContextGetBytesPerRow(mCg); michael@0: size_t height = CGBitmapContextGetHeight(mCg); michael@0: michael@0: size_t bufLen = BufferSizeFromStrideAndHeight(stride, height); michael@0: if (bufLen == 0) { michael@0: mDataHolder.Dealloc(); michael@0: mData = nullptr; michael@0: } else { michael@0: static_assert(sizeof(decltype(mDataHolder[0])) == 1, michael@0: "mDataHolder.Realloc() takes an object count, so its objects must be 1-byte sized if we use bufLen"); michael@0: mDataHolder.Realloc(/* actually an object count */ bufLen); michael@0: mData = mDataHolder; michael@0: michael@0: // copy out the data from the CGBitmapContext michael@0: // we'll maintain ownership of mData until michael@0: // we transfer it to mImage michael@0: memcpy(mData, CGBitmapContextGetData(mCg), bufLen); michael@0: } michael@0: michael@0: // drop the current image for the data associated with the CGBitmapContext michael@0: if (mImage) michael@0: CGImageRelease(mImage); michael@0: mImage = nullptr; michael@0: michael@0: mCg = nullptr; michael@0: mDrawTarget = nullptr; michael@0: } michael@0: } michael@0: michael@0: SourceSurfaceCGBitmapContext::~SourceSurfaceCGBitmapContext() michael@0: { michael@0: if (mImage) michael@0: CGImageRelease(mImage); michael@0: } michael@0: michael@0: SourceSurfaceCGIOSurfaceContext::SourceSurfaceCGIOSurfaceContext(DrawTargetCG *aDrawTarget) michael@0: { michael@0: CGContextRef cg = (CGContextRef)aDrawTarget->GetNativeSurface(NativeSurfaceType::CGCONTEXT_ACCELERATED); michael@0: michael@0: RefPtr surf = MacIOSurface::IOSurfaceContextGetSurface(cg); michael@0: michael@0: mFormat = aDrawTarget->GetFormat(); michael@0: mSize.width = surf->GetWidth(); michael@0: mSize.height = surf->GetHeight(); michael@0: michael@0: // TODO use CreateImageFromIOSurfaceContext instead of reading back the surface michael@0: //mImage = MacIOSurface::CreateImageFromIOSurfaceContext(cg); michael@0: mImage = nullptr; michael@0: michael@0: aDrawTarget->Flush(); michael@0: surf->Lock(); michael@0: size_t bytesPerRow = surf->GetBytesPerRow(); michael@0: size_t ioHeight = surf->GetHeight(); michael@0: void* ioData = surf->GetBaseAddress(); michael@0: // XXX If the width is much less then the stride maybe michael@0: // we should repack the image? michael@0: mData = malloc(ioHeight*bytesPerRow); michael@0: memcpy(mData, ioData, ioHeight*(bytesPerRow)); michael@0: mStride = bytesPerRow; michael@0: surf->Unlock(); michael@0: } michael@0: michael@0: void SourceSurfaceCGIOSurfaceContext::EnsureImage() const michael@0: { michael@0: // TODO Use CreateImageFromIOSurfaceContext and remove this michael@0: michael@0: // Instead of using CGBitmapContextCreateImage we create michael@0: // a CGImage around the data associated with the CGBitmapContext michael@0: // we do this to avoid the vm_copy that CGBitmapContextCreateImage. michael@0: // vm_copy tends to cause all sorts of unexpected performance problems michael@0: // because of the mm tricks that vm_copy does. Using a regular michael@0: // memcpy when the bitmap context is modified gives us more predictable michael@0: // performance characteristics. michael@0: if (!mImage) { michael@0: mImage = CreateCGImage(mData, mData, mSize, mStride, SurfaceFormat::B8G8R8A8); michael@0: } michael@0: michael@0: } michael@0: michael@0: IntSize michael@0: SourceSurfaceCGIOSurfaceContext::GetSize() const michael@0: { michael@0: return mSize; michael@0: } michael@0: michael@0: void michael@0: SourceSurfaceCGIOSurfaceContext::DrawTargetWillChange() michael@0: { michael@0: } michael@0: michael@0: SourceSurfaceCGIOSurfaceContext::~SourceSurfaceCGIOSurfaceContext() michael@0: { michael@0: if (mImage) michael@0: CGImageRelease(mImage); michael@0: else michael@0: free(mData); michael@0: } michael@0: michael@0: unsigned char* michael@0: SourceSurfaceCGIOSurfaceContext::GetData() michael@0: { michael@0: return (unsigned char*)mData; michael@0: } michael@0: michael@0: } michael@0: }