source: trunk/TextSubCodec.c @ 1189

Revision 1189, 16.7 KB checked in by astrange, 4 years ago (diff)

SSA: Render in sRGB instead of Generic RGB Profile.
Also attach the sRGB profile to each track.
...but QT still complains about it not being color-tagged.

Line 
1/*
2 * TextSubCodec.c
3 * Created by David Conrad on 3/21/06.
4 *
5 * This file is part of Perian.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22#if __MACH__
23    #include <Carbon/Carbon.h>
24    #include <QuickTime/QuickTime.h>
25#else
26    #include <ConditionalMacros.h>
27    #include <Endian.h>
28    #include <ImageCodec.h>
29#endif
30
31#include "PerianResourceIDs.h"
32#include "SubATSUIRenderer.h"
33#include "CommonUtils.h"
34
35// Data structures
36typedef struct TextSubGlobalsRecord {
37        ComponentInstance               self;
38        ComponentInstance               delegateComponent;
39        ComponentInstance               target;
40        OSType**                                wantedDestinationPixelTypeH;
41        ImageCodecMPDrawBandUPP drawBandUPP;
42       
43        CGColorSpaceRef         colorSpace;
44
45        SubtitleRendererPtr             ssa;
46        Boolean                                 translateSRT;
47} TextSubGlobalsRecord, *TextSubGlobals;
48
49typedef struct {
50        long            width;
51        long            height;
52        long            depth;
53    long        dataSize;
54   
55    Ptr             baseAddr;
56        OSType                  pixelFormat;
57} TextSubDecompressRecord;
58
59// Setup required for ComponentDispatchHelper.c
60#define IMAGECODEC_BASENAME()           TextSubCodec
61#define IMAGECODEC_GLOBALS()            TextSubGlobals storage
62
63#define CALLCOMPONENT_BASENAME()        IMAGECODEC_BASENAME()
64#define CALLCOMPONENT_GLOBALS()         IMAGECODEC_GLOBALS()
65
66#define COMPONENT_UPP_PREFIX()          uppImageCodec
67#define COMPONENT_DISPATCH_FILE         "TextSubCodecDispatch.h"
68#define COMPONENT_SELECT_PREFIX()       kImageCodec
69
70#define GET_DELEGATE_COMPONENT()        (storage->delegateComponent)
71
72#if __MACH__
73        #include <CoreServices/Components.k.h>
74        #include <QuickTime/ImageCodec.k.h>
75        #include <QuickTime/ComponentDispatchHelper.c>
76#else
77        #include <Components.k.h>
78        #include <ImageCodec.k.h>
79        #include <ComponentDispatchHelper.c>
80#endif
81
82#define kNumPixelFormatsSupportedTextSub 2
83
84static CFMutableStringRef CFStringCreateMutableWithBytes(CFAllocatorRef alloc, char *cStr, size_t size, CFStringEncoding encoding) {
85        CFStringRef                s1 = CFStringCreateWithBytes(alloc,(UInt8*)cStr,size,encoding,false);
86        if (!s1) return NULL;
87        CFMutableStringRef s2 = CFStringCreateMutableCopy(alloc,0,s1);
88        CFRelease(s1);
89        return s2;
90}
91
92/* -- This Image Decompressor Uses the Base Image Decompressor Component --
93        The base image decompressor is an Apple-supplied component
94        that makes it easier for developers to create new decompressors.
95        The base image decompressor does most of the housekeeping and
96        interface functions required for a QuickTime decompressor component,
97        including scheduling for asynchronous decompression.
98*/
99
100// Component Open Request - Required
101pascal ComponentResult TextSubCodecOpen(TextSubGlobals glob, ComponentInstance self)
102{
103        ComponentResult err;
104
105        // Allocate memory for our globals, set them up and inform the component manager that we've done so
106        glob = (TextSubGlobals)NewPtrClear(sizeof(TextSubGlobalsRecord));
107        if (err = MemError()) goto bail;
108
109        SetComponentInstanceStorage(self, (Handle)glob);
110
111        glob->self = self;
112        glob->target = self;
113        glob->wantedDestinationPixelTypeH = (OSType **)NewHandleClear((kNumPixelFormatsSupportedTextSub+1) * sizeof(OSType));
114        if (err = MemError()) goto bail;
115        glob->drawBandUPP = NULL;
116        glob->ssa = NULL;
117        glob->colorSpace = NULL;
118        glob->translateSRT = true;
119       
120        // Open and target an instance of the base decompressor as we delegate
121        // most of our calls to the base decompressor instance
122        err = OpenADefaultComponent(decompressorComponentType, kBaseCodecType, &glob->delegateComponent);
123        if (err) goto bail;
124
125        ComponentSetTarget(glob->delegateComponent, self);
126
127bail:
128        return err;
129}
130
131// Component Close Request - Required
132pascal ComponentResult TextSubCodecClose(TextSubGlobals glob, ComponentInstance self)
133{
134        // Make sure to close the base component and dealocate our storage
135        if (glob) {
136                if (glob->delegateComponent) {
137                        CloseComponent(glob->delegateComponent);
138                }
139                if (glob->wantedDestinationPixelTypeH) {
140                        DisposeHandle((Handle)glob->wantedDestinationPixelTypeH);
141                }
142                if (glob->drawBandUPP) {
143                        DisposeImageCodecMPDrawBandUPP(glob->drawBandUPP);
144                }
145                if (glob->ssa) SubDisposeRenderer(glob->ssa);
146                DisposePtr((Ptr)glob);
147        }
148
149        return noErr;
150}
151
152// Component Version Request - Required
153pascal ComponentResult TextSubCodecVersion(TextSubGlobals glob)
154{
155#pragma unused(glob)
156       
157    return kTextSubCodecVersion;
158}
159
160// Component Target Request
161//              Allows another component to "target" you i.e., you call another component whenever
162// you would call yourself (as a result of your component being used by another component)
163pascal ComponentResult TextSubCodecTarget(TextSubGlobals glob, ComponentInstance target)
164{
165        glob->target = target;
166        return noErr;
167}
168
169// Component GetMPWorkFunction Request
170//              Allows your image decompressor component to perform asynchronous decompression
171// in a single MP task by taking advantage of the Base Decompressor. If you implement
172// this selector, your DrawBand function must be MP-safe. MP safety means not
173// calling routines that may move or purge memory and not calling any routines which
174// might cause 68K code to be executed.
175pascal ComponentResult TextSubCodecGetMPWorkFunction(TextSubGlobals glob, ComponentMPWorkFunctionUPP *workFunction, void **refCon)
176{
177        if (NULL == glob->drawBandUPP)
178                glob->drawBandUPP = NewImageCodecMPDrawBandUPP((ImageCodecMPDrawBandProcPtr)TextSubCodecDrawBand);
179               
180        return ImageCodecGetBaseMPWorkFunction(glob->delegateComponent, workFunction, refCon, glob->drawBandUPP, glob);
181}
182
183#pragma mark-
184
185// ImageCodecInitialize
186//              The first function call that your image decompressor component receives from the base image
187// decompressor is always a call to ImageCodecInitialize . In response to this call, your image decompressor
188// component returns an ImageSubCodecDecompressCapabilities structure that specifies its capabilities.
189pascal ComponentResult TextSubCodecInitialize(TextSubGlobals glob, ImageSubCodecDecompressCapabilities *cap)
190{
191#pragma unused(glob)
192
193        // Secifies the size of the ImageSubCodecDecompressRecord structure
194        // and say we can support asyncronous decompression
195        // With the help of the base image decompressor, any image decompressor
196        // that uses only interrupt-safe calls for decompression operations can
197        // support asynchronous decompression.
198        cap->decompressRecordSize = sizeof(TextSubDecompressRecord);
199        cap->canAsync = true;
200       
201        cap->subCodecIsMultiBufferAware = true;
202        cap->subCodecSupportsDecodeSmoothing = true;
203
204        return noErr;
205}
206
207// ImageCodecPreflight
208//              The base image decompressor gets additional information about the capabilities of your image
209// decompressor component by calling ImageCodecPreflight. The base image decompressor uses this
210// information when responding to a call to the ImageCodecPredecompress function,
211// which the ICM makes before decompressing an image. You are required only to provide values for
212// the wantedDestinationPixelSize and wantedDestinationPixelTypes fields and can also modify other
213// fields if necessary.
214pascal ComponentResult TextSubCodecPreflight(TextSubGlobals glob, CodecDecompressParams *p)
215{
216        CodecCapabilities *capabilities = p->capabilities;
217        OSTypePtr         formats = *glob->wantedDestinationPixelTypeH;
218
219        // Fill in formats for wantedDestinationPixelTypeH
220        // Terminate with an OSType value 0  - see IceFloe #7
221        // http://developer.apple.com/quicktime/icefloe/dispatch007.html
222   
223    // we want ARGB because Quartz can use it easily
224    // Todo: add other possible pixel formats Quartz can handle
225    *formats++  = k32RGBAPixelFormat;
226        *formats++      = k32ARGBPixelFormat;
227       
228        // Specify the minimum image band height supported by the component
229        // bandInc specifies a common factor of supported image band heights -
230        // if your component supports only image bands that are an even
231    // multiple of some number of pixels high report this common factor in bandInc
232        capabilities->bandMin = (**p->imageDescription).height;
233        capabilities->bandInc = capabilities->bandMin;
234
235        // Indicate the wanted destination using the wantedDestinationPixelTypeH previously set up
236        capabilities->wantedPixelSize  = 0;     
237   
238        p->wantedDestinationPixelTypes = glob->wantedDestinationPixelTypeH;
239
240        // Specify the number of pixels the image must be extended in width and height if
241        // the component cannot accommodate the image at its given width and height
242        capabilities->extendWidth = 0;
243        capabilities->extendHeight = 0;
244       
245        capabilities->flags |= codecCanAsync | codecCanAsyncWhen | codecCanScale;
246        capabilities->flags2 |= codecDrawsHigherQualityScaled;   
247       
248        if (!glob->ssa) {
249                if (!glob->colorSpace)
250                        glob->colorSpace = GetSRGBColorSpace();
251               
252                if ((**p->imageDescription).cType == kSubFormatSSA) {
253                        long count;
254                        glob->translateSRT = false;
255                       
256                        CountImageDescriptionExtensionType(p->imageDescription,kSubFormatSSA,&count);
257                        if (count == 1) {
258                                Handle ssaheader;
259                                GetImageDescriptionExtension(p->imageDescription,&ssaheader,kSubFormatSSA,1);
260                               
261                                glob->ssa = SubInitSSA(*ssaheader, GetHandleSize(ssaheader), (**p->imageDescription).width, (**p->imageDescription).height);
262                        } 
263                } 
264               
265                if (!glob->ssa) glob->ssa = SubInitNonSSA((**p->imageDescription).width,(**p->imageDescription).height);
266        }
267       
268        return noErr;
269}
270
271// ImageCodecBeginBand
272//              The ImageCodecBeginBand function allows your image decompressor component to save information about
273// a band before decompressing it. This function is never called at interrupt time. The base image decompressor
274// preserves any changes your component makes to any of the fields in the ImageSubCodecDecompressRecord
275// or CodecDecompressParams structures. If your component supports asynchronous scheduled decompression, it
276// may receive more than one ImageCodecBeginBand call before receiving an ImageCodecDrawBand call.
277pascal ComponentResult TextSubCodecBeginBand(TextSubGlobals glob, CodecDecompressParams *p, ImageSubCodecDecompressRecord *drp, long flags)
278{
279        TextSubDecompressRecord *myDrp = (TextSubDecompressRecord *)drp->userDecompressRecord;
280       
281        // Let base codec know that all our frames are key frames (a.k.a., sync samples)
282        // This allows the base codec to perform frame dropping on our behalf if needed
283    drp->frameType = kCodecFrameTypeKey;
284
285        myDrp->pixelFormat = p->dstPixMap.pixelFormat;
286        myDrp->width = p->dstRect.right - p->dstRect.left;
287        myDrp->height = p->dstRect.bottom - p->dstRect.top;
288        myDrp->depth = (**p->imageDescription).depth;
289    myDrp->dataSize = p->bufferSize;
290       
291        return noErr;
292}
293
294// ImageCodecDrawBand
295//              The base image decompressor calls your image decompressor component's ImageCodecDrawBand function
296// to decompress a band or frame. Your component must implement this function. If the ImageSubCodecDecompressRecord
297// structure specifies a progress function or data-loading function, the base image decompressor will never call ImageCodecDrawBand
298// at interrupt time. If the ImageSubCodecDecompressRecord structure specifies a progress function, the base image decompressor
299// handles codecProgressOpen and codecProgressClose calls, and your image decompressor component must not implement these functions.
300// If not, the base image decompressor may call the ImageCodecDrawBand function at interrupt time.
301// When the base image decompressor calls your ImageCodecDrawBand function, your component must perform the decompression specified
302// by the fields of the ImageSubCodecDecompressRecord structure. The structure includes any changes your component made to it
303// when performing the ImageCodecBeginBand function. If your component supports asynchronous scheduled decompression,
304// it may receive more than one ImageCodecBeginBand call before receiving an ImageCodecDrawBand call.
305pascal ComponentResult TextSubCodecDrawBand(TextSubGlobals glob, ImageSubCodecDecompressRecord *drp)
306{
307        TextSubDecompressRecord *myDrp = (TextSubDecompressRecord *)drp->userDecompressRecord;
308        CGImageAlphaInfo alphaFormat = (myDrp->pixelFormat == k32ARGBPixelFormat) ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaPremultipliedLast;
309
310    CGContextRef c = CGBitmapContextCreate(drp->baseAddr, myDrp->width, myDrp->height,
311                                                                                   8, drp->rowBytes,  glob->colorSpace,
312                                                                                   alphaFormat);
313       
314        CGContextClearRect(c, CGRectMake(0,0, myDrp->width, myDrp->height));
315       
316        CFMutableStringRef buf;
317       
318        if (drp->codecData[0] == '\n' && myDrp->dataSize == 1) goto leave; // skip empty packets
319       
320        if (glob->translateSRT) {
321                buf = CFStringCreateMutableWithBytes(NULL, drp->codecData, myDrp->dataSize, kCFStringEncodingUTF8);
322                if (!buf) goto leave;
323                CFStringFindAndReplace(buf, CFSTR("<i>"),  CFSTR("{\\i1}"), CFRangeMake(0,CFStringGetLength(buf)), 0);
324                CFStringFindAndReplace(buf, CFSTR("</i>"), CFSTR("{\\i0}"), CFRangeMake(0,CFStringGetLength(buf)), 0);
325                CFStringFindAndReplace(buf, CFSTR("<"),    CFSTR("{"),      CFRangeMake(0,CFStringGetLength(buf)), 0);
326                CFStringFindAndReplace(buf, CFSTR(">"),    CFSTR("}"),      CFRangeMake(0,CFStringGetLength(buf)), 0);
327        } else {
328                buf = (CFMutableStringRef)CFStringCreateWithBytes(NULL, (UInt8*)drp->codecData, myDrp->dataSize, kCFStringEncodingUTF8, false);
329                if (!buf) goto leave;
330        }
331       
332        SubRenderPacket(glob->ssa,c,buf,myDrp->width,myDrp->height);
333       
334        if (IsTransparentSubtitleHackEnabled()) {
335                // Map 8-bit alpha (graphicsModePreBlendAlpha) to 1-bit alpha (transparent)
336                // Pretty much this is just mapping all opaque black to (1,1,1,255)
337                // Leaves ugly borders where AAing turned into opaque colors, but that's harder to deal with
338                Ptr p = drp->baseAddr;
339                int y, x;
340                UInt32 alphaMask = EndianU32_BtoN((myDrp->pixelFormat == k32ARGBPixelFormat) ? 0xFF000000 : 0xFF),
341                       replacement = EndianU32_BtoN((myDrp->pixelFormat == k32ARGBPixelFormat) ? 0xFF010101 : 0x010101FF);
342
343                for (y = 0; y < myDrp->height; y++) {
344                        UInt32 *p32 = (UInt32*)p;
345                        for (x = 0; x < myDrp->width; x++) {
346                                UInt32 px = *p32;
347                               
348                                // if px is black, and opaque (alpha == 255)
349                                if (!(px & ~alphaMask) && (px & alphaMask == alphaMask)) {
350                                        // then set it to not-quite-black so it'll show up
351                                        *p32 = replacement;
352                                }
353                               
354                                p32++;
355                        }
356                       
357                        p += drp->rowBytes;
358                }
359        }
360               
361        CFRelease(buf);
362       
363leave:
364        CGContextRelease(c);
365        return noErr;
366}
367
368// ImageCodecEndBand
369//              The ImageCodecEndBand function notifies your image decompressor component that decompression of a band has finished or
370// that it was terminated by the Image Compression Manager. Your image decompressor component is not required to implement
371// the ImageCodecEndBand function. The base image decompressor may call the ImageCodecEndBand function at interrupt time.
372// After your image decompressor component handles an ImageCodecEndBand call, it can perform any tasks that are required
373// when decompression is finished, such as disposing of data structures that are no longer needed. Because this function
374// can be called at interrupt time, your component cannot use this function to dispose of data structures; this
375// must occur after handling the function. The value of the result parameter should be set to noErr if the band or frame was
376// drawn successfully. If it is any other value, the band or frame was not drawn.
377pascal ComponentResult TextSubCodecEndBand(TextSubGlobals glob, ImageSubCodecDecompressRecord *drp, OSErr result, long flags)
378{
379#pragma unused(glob, drp,result, flags)
380       
381        return noErr;
382}
383
384// ImageCodecGetSourceDataGammaLevel
385// Returns 1.8, the gamma for sRGB.
386pascal ComponentResult TextSubCodecGetSourceDataGammaLevel(TextSubGlobals glob, Fixed *sourceDataGammaLevel)
387{
388        *sourceDataGammaLevel = FloatToFixed(2.2);
389        return noErr;
390}
391
392// ImageCodecGetCodecInfo
393//              Your component receives the ImageCodecGetCodecInfo request whenever an application calls the Image Compression Manager's GetCodecInfo function.
394// Your component should return a formatted compressor information structure defining its capabilities.
395// Both compressors and decompressors may receive this request.
396pascal ComponentResult TextSubCodecGetCodecInfo(TextSubGlobals glob, CodecInfo *info)
397{
398        OSErr err = noErr;
399        ComponentDescription desc;
400        short resid;
401       
402        GetComponentInfo((Component)glob->self, &desc, 0, 0, 0);
403       
404        if (desc.componentSubType == kSubFormatSSA)
405                resid = kSSASubCodecResourceID;
406        else
407                resid = kTextSubCodecResourceID;
408
409        if (info == NULL) {
410                err = paramErr;
411        } else {
412                CodecInfo **tempCodecInfo;
413
414                err = GetComponentResource((Component)glob->self, codecInfoResourceType, resid, (Handle *)&tempCodecInfo);
415                if (err == noErr) {
416                        *info = **tempCodecInfo;
417                        DisposeHandle((Handle)tempCodecInfo);
418                }
419        }
420
421        return err;
422}
Note: See TracBrowser for help on using the repository browser.