1/*
2 * Copyright (C) 2010 Apple, Inc.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25#include "config.h"
26
27#include "QTDecompressionSession.h"
28
29#include <ImageCompression.h>
30#include <algorithm>
31
32class QTDecompressionSessionClient {
33public:
34    static void trackingCallback(void *decompressionTrackingRefCon, OSStatus,
35        ICMDecompressionTrackingFlags decompressionTrackingFlags, CVPixelBufferRef pixelBuffer,
36        TimeValue64, TimeValue64, ICMValidTimeFlags, void *, void *)
37    {
38        QTDecompressionSession* session = static_cast<QTDecompressionSession*>(decompressionTrackingRefCon);
39        ASSERT(session);
40
41        if (decompressionTrackingFlags & kICMDecompressionTracking_FrameDecoded)
42            session->m_latestFrame = QTPixelBuffer(pixelBuffer);
43    }
44};
45
46PassOwnPtr<QTDecompressionSession> QTDecompressionSession::create(unsigned long pixelFormat, size_t width, size_t height)
47{
48    return adoptPtr(new QTDecompressionSession(pixelFormat, width, height));
49}
50
51QTDecompressionSession::QTDecompressionSession(unsigned long pixelFormat, size_t width, size_t height)
52    : m_session(0)
53    , m_pixelFormat(pixelFormat)
54    , m_width(width)
55    , m_height(height)
56{
57    initializeSession();
58}
59
60QTDecompressionSession::~QTDecompressionSession()
61{
62    if (m_session)
63        ICMDecompressionSessionRelease(m_session);
64}
65
66void QTDecompressionSession::initializeSession()
67{
68    if (m_session)
69        return;
70
71    ICMPixelFormatInfo pixelFormatInfo = {sizeof(ICMPixelFormatInfo), 0};
72    if (ICMGetPixelFormatInfo(m_pixelFormat, &pixelFormatInfo) != noErr) {
73        // The ICM does not know anything about the pixelFormat contained in
74        // the pixel buffer, so it won't be able to convert it to RGBA.
75        return;
76    }
77
78    // The depth and cType fields of the ImageDescriptionHandle are filled
79    // out according to the instructions in Technical Q&A QA1183:
80    // http://developer.apple.com/library/mac/#qa/qa2001/qa1183.html
81    bool isIndexed = pixelFormatInfo.formatFlags & kICMPixelFormatIsIndexed;
82    bool isQD = pixelFormatInfo.formatFlags & kICMPixelFormatIsSupportedByQD;
83    bool isMonochrome = pixelFormatInfo.formatFlags & kICMPixelFormatIsMonochrome;
84    bool hasAlpha = pixelFormatInfo.formatFlags & kICMPixelFormatHasAlphaChannel;
85
86    unsigned int depth = 24; // The default depth is 24.
87    if (hasAlpha)
88        depth = 32; // Any pixel format with alpha gets a depth of 32.
89    else if (isMonochrome) {
90        // Grayscale pixel formats get depths 33 through 40, depending
91        // on their bits per pixel. Yes, this means that 16-bit grayscale
92        // and 8-bit grayscale have the same pixel depth.
93        depth = 32 + std::min<unsigned int>(8, pixelFormatInfo.bitsPerPixel[0]);
94    } else if (isIndexed) {
95        // Indexed pixel formats get a depth of 1 through 8, depending on
96        // the their bits per pixel.
97        depth = pixelFormatInfo.bitsPerPixel[0];
98    }
99
100    // If QuickDraw supports the given pixel format, the cType should be kRawCodecType.
101    // Otherwise, use the pixel format code for the cType.  We are assuming the pixel
102    // buffer is uncompressed.
103    unsigned long cType = isQD ? kRawCodecType : m_pixelFormat;
104
105    ImageDescriptionHandle description = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription));
106    (**description).idSize = sizeof(ImageDescription);
107    (**description).cType = cType;
108    (**description).version = 2;
109    (**description).spatialQuality = codecLosslessQuality;
110    (**description).width = m_width;
111    (**description).height = m_height;
112    (**description).hRes = 72 << 16; // 72 DPI as a fixed-point number
113    (**description).vRes = 72 << 16; // 72 DPI as a fixed-point number
114    (**description).frameCount = 1;
115    (**description).depth =  depth;
116    (**description).clutID = -1;
117
118    // Create the mandatory ICMDecompressionSessionOptions, but leave
119    // all the default values.
120    ICMDecompressionSessionOptionsRef options = 0;
121    ICMDecompressionSessionOptionsCreate(kCFAllocatorDefault, &options);
122
123    CFDictionaryRef pixelBufferAttributes = QTPixelBuffer::createPixelBufferAttributesDictionary(QTPixelBuffer::ConfigureForCGImage);
124
125    ICMDecompressionTrackingCallbackRecord callback = {
126        QTDecompressionSessionClient::trackingCallback,
127        this,
128    };
129
130    ICMDecompressionSessionCreate(kCFAllocatorDefault,
131        description,
132        options,
133        pixelBufferAttributes,
134        &callback,
135        &m_session);
136
137    if (pixelBufferAttributes)
138        CFRelease(pixelBufferAttributes);
139
140    ICMDecompressionSessionOptionsRelease(options);
141    DisposeHandle((Handle)description);
142}
143
144bool QTDecompressionSession::canDecompress(QTPixelBuffer inBuffer)
145{
146    return m_session
147        && inBuffer.pixelFormatType() == m_pixelFormat
148        && inBuffer.width() == m_width
149        && inBuffer.height() == m_height;
150}
151
152QTPixelBuffer QTDecompressionSession::decompress(QTPixelBuffer inBuffer)
153{
154    if (!canDecompress(inBuffer))
155        return QTPixelBuffer();
156
157    inBuffer.lockBaseAddress();
158    ICMDecompressionSessionDecodeFrame(m_session,
159        static_cast<UInt8*>(inBuffer.baseAddress()),
160        inBuffer.dataSize(),
161        0, // frameOptions
162        0, // frameTime
163        0); // sourceFrameRefCon
164
165    // Because we passed in 0 for frameTime, the above function
166    // is synchronous, and the client callback will have been
167    // called before the function returns, and m_latestFrame
168    // will contain the newly decompressed frame.
169    return m_latestFrame;
170}
171