555 lines
15 KiB
Objective-C
555 lines
15 KiB
Objective-C
/*
|
|
* Code to use Quicktime to load images/movies as texture.
|
|
*
|
|
* ***** BEGIN GPL LICENSE BLOCK *****
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*
|
|
* The Original Code is written by Rob Haarsma (phase)
|
|
*
|
|
* Contributor(s): Stefan Gartner (sgefant)
|
|
* Damien Plisson 11/2009
|
|
*
|
|
* ***** END GPL LICENSE BLOCK *****
|
|
*/
|
|
#ifdef WITH_QUICKTIME
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "IMB_anim.h"
|
|
#include "BLO_sys_types.h"
|
|
#include "BKE_global.h"
|
|
|
|
#include "BLI_dynstr.h"
|
|
#include "BLI_path_util.h"
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
#import <QTKit/QTKit.h>
|
|
|
|
#include "quicktime_import.h"
|
|
#include "quicktime_export.h"
|
|
|
|
// quicktime structure definition
|
|
// this structure is part of the anim struct
|
|
|
|
typedef struct _QuicktimeMovie {
|
|
QTMovie *movie;
|
|
QTMedia *media;
|
|
|
|
long durationTime;
|
|
long durationScale;
|
|
long framecount;
|
|
|
|
|
|
ImBuf *ibuf;
|
|
|
|
long previousPosition;
|
|
|
|
} QuicktimeMovie;
|
|
|
|
|
|
#define QTIME_DEBUG 0
|
|
|
|
|
|
void quicktime_init(void)
|
|
{
|
|
G.have_quicktime = TRUE;
|
|
}
|
|
|
|
|
|
void quicktime_exit(void)
|
|
{
|
|
if (G.have_quicktime) {
|
|
free_qtcomponentdata();
|
|
}
|
|
}
|
|
|
|
|
|
int anim_is_quicktime (const char *name)
|
|
{
|
|
NSAutoreleasePool *pool;
|
|
|
|
// don't let quicktime movie import handle these
|
|
if (BLI_testextensie(name, ".swf") ||
|
|
BLI_testextensie(name, ".txt") ||
|
|
BLI_testextensie(name, ".mpg") ||
|
|
BLI_testextensie(name, ".avi") || // wouldn't be appropriate ;)
|
|
BLI_testextensie(name, ".mov") || // disabled, suboptimal decoding speed
|
|
BLI_testextensie(name, ".mp4") || // disabled, suboptimal decoding speed
|
|
BLI_testextensie(name, ".m4v") || // disabled, suboptimal decoding speed
|
|
BLI_testextensie(name, ".tga") ||
|
|
BLI_testextensie(name, ".png") ||
|
|
BLI_testextensie(name, ".bmp") ||
|
|
BLI_testextensie(name, ".jpg") ||
|
|
BLI_testextensie(name, ".wav") ||
|
|
BLI_testextensie(name, ".zip") ||
|
|
BLI_testextensie(name, ".mp3"))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (QTIME_DEBUG) printf("qt: checking as movie: %s\n", name);
|
|
|
|
pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
if ([QTMovie canInitWithFile:[NSString stringWithCString:name
|
|
encoding:[NSString defaultCStringEncoding]]])
|
|
{
|
|
[pool drain];
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
[pool drain];
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
void free_anim_quicktime (struct anim *anim)
|
|
{
|
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
if (anim == NULL) return;
|
|
if (anim->qtime == NULL) return;
|
|
|
|
if(anim->qtime->ibuf)
|
|
IMB_freeImBuf(anim->qtime->ibuf);
|
|
|
|
[anim->qtime->media release];
|
|
[anim->qtime->movie release];
|
|
|
|
[QTMovie exitQTKitOnThread];
|
|
|
|
if(anim->qtime) MEM_freeN (anim->qtime);
|
|
|
|
anim->qtime = NULL;
|
|
|
|
anim->duration = 0;
|
|
|
|
[pool drain];
|
|
}
|
|
|
|
static ImBuf * nsImageToiBuf(NSImage *sourceImage, int width, int height)
|
|
{
|
|
ImBuf *ibuf = NULL;
|
|
uchar *rasterRGB = NULL;
|
|
uchar *rasterRGBA = NULL;
|
|
uchar *toIBuf = NULL;
|
|
int x, y, to_i, from_i;
|
|
NSSize bitmapSize;
|
|
NSBitmapImageRep *blBitmapFormatImageRGB,*blBitmapFormatImageRGBA, *bitmapImage = nil;
|
|
NSEnumerator *enumerator;
|
|
NSImageRep *representation;
|
|
|
|
ibuf = IMB_allocImBuf (width, height, 32, IB_rect);
|
|
if (!ibuf) {
|
|
if (QTIME_DEBUG) {
|
|
printf("quicktime_import: could not allocate memory for the image.\n");
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*Get the bitmap of the image*/
|
|
enumerator = [[sourceImage representations] objectEnumerator];
|
|
while ((representation = [enumerator nextObject])) {
|
|
if ([representation isKindOfClass:[NSBitmapImageRep class]]) {
|
|
bitmapImage = (NSBitmapImageRep *)representation;
|
|
break;
|
|
}
|
|
}
|
|
if (bitmapImage == nil) return NULL;
|
|
|
|
if (([bitmapImage bitsPerPixel] == 32) && (([bitmapImage bitmapFormat] & 0x5) == 0)
|
|
&& ![bitmapImage isPlanar]) {
|
|
/* Try a fast copy if the image is a meshed RGBA 32bit bitmap*/
|
|
toIBuf = (uchar *)ibuf->rect;
|
|
rasterRGB = (uchar *)[bitmapImage bitmapData];
|
|
for (y = 0; y < height; y++) {
|
|
to_i = (height-y-1)*width;
|
|
from_i = y*width;
|
|
memcpy(toIBuf+4*to_i, rasterRGB+4*from_i, 4*width);
|
|
}
|
|
}
|
|
else {
|
|
|
|
bitmapSize.width = width;
|
|
bitmapSize.height = height;
|
|
|
|
/* Tell cocoa image resolution is same as current system one */
|
|
[bitmapImage setSize:bitmapSize];
|
|
|
|
/* Convert the image in a RGBA 32bit format */
|
|
/* As Core Graphics does not support contextes with non premutliplied alpha,
|
|
we need to get alpha key values in a separate batch */
|
|
|
|
/* First get RGB values w/o Alpha to avoid pre-multiplication, 32bit but last byte is unused */
|
|
blBitmapFormatImageRGB = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
|
|
pixelsWide:width
|
|
pixelsHigh:height
|
|
bitsPerSample:8 samplesPerPixel:3 hasAlpha:NO isPlanar:NO
|
|
colorSpaceName:NSDeviceRGBColorSpace
|
|
bitmapFormat:0
|
|
bytesPerRow:4*width
|
|
bitsPerPixel:32/*RGB format padded to 32bits*/];
|
|
|
|
[NSGraphicsContext saveGraphicsState];
|
|
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGB]];
|
|
[bitmapImage draw];
|
|
[NSGraphicsContext restoreGraphicsState];
|
|
|
|
rasterRGB = (uchar *)[blBitmapFormatImageRGB bitmapData];
|
|
if (rasterRGB == NULL) {
|
|
[blBitmapFormatImageRGB release];
|
|
return NULL;
|
|
}
|
|
|
|
/* Then get Alpha values by getting the RGBA image (that is premultiplied btw) */
|
|
blBitmapFormatImageRGBA = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
|
|
pixelsWide:width
|
|
pixelsHigh:height
|
|
bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO
|
|
colorSpaceName:NSDeviceRGBColorSpace
|
|
bitmapFormat:0
|
|
bytesPerRow:4*width
|
|
bitsPerPixel:32/* RGBA */];
|
|
|
|
[NSGraphicsContext saveGraphicsState];
|
|
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGBA]];
|
|
[bitmapImage draw];
|
|
[NSGraphicsContext restoreGraphicsState];
|
|
|
|
rasterRGBA = (uchar *)[blBitmapFormatImageRGBA bitmapData];
|
|
if (rasterRGBA == NULL) {
|
|
[blBitmapFormatImageRGB release];
|
|
[blBitmapFormatImageRGBA release];
|
|
return NULL;
|
|
}
|
|
|
|
/*Copy the image to ibuf, flipping it vertically*/
|
|
toIBuf = (uchar *)ibuf->rect;
|
|
for (y = 0; y < height; y++) {
|
|
for (x = 0; x < width; x++) {
|
|
to_i = (height-y-1)*width + x;
|
|
from_i = y*width + x;
|
|
|
|
toIBuf[4*to_i] = rasterRGB[4*from_i]; /* R */
|
|
toIBuf[4*to_i+1] = rasterRGB[4*from_i+1]; /* G */
|
|
toIBuf[4*to_i+2] = rasterRGB[4*from_i+2]; /* B */
|
|
toIBuf[4*to_i+3] = rasterRGBA[4*from_i+3]; /* A */
|
|
}
|
|
}
|
|
|
|
[blBitmapFormatImageRGB release];
|
|
[blBitmapFormatImageRGBA release];
|
|
}
|
|
|
|
return ibuf;
|
|
}
|
|
|
|
ImBuf * qtime_fetchibuf (struct anim *anim, int position)
|
|
{
|
|
NSImage *frameImage;
|
|
QTTime time;
|
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
ImBuf *ibuf;
|
|
|
|
if (anim == NULL) {
|
|
return (NULL);
|
|
}
|
|
|
|
if (position == anim->qtime->previousPosition+1) { //Optimize sequential read
|
|
[anim->qtime->movie stepForward];
|
|
frameImage = [anim->qtime->movie currentFrameImage];
|
|
anim->qtime->previousPosition++;
|
|
}
|
|
else {
|
|
time.timeScale = anim->qtime->durationScale;
|
|
time.timeValue = (anim->qtime->durationTime * position) / anim->qtime->framecount;
|
|
|
|
[anim->qtime->movie setCurrentTime:time];
|
|
frameImage = [anim->qtime->movie currentFrameImage];
|
|
|
|
anim->qtime->previousPosition = position;
|
|
}
|
|
|
|
if (frameImage == nil) {
|
|
if(QTIME_DEBUG) printf ("Error reading frame from Quicktime");
|
|
[pool drain];
|
|
return NULL;
|
|
}
|
|
|
|
ibuf = nsImageToiBuf(frameImage,anim->x, anim->y);
|
|
[pool drain];
|
|
|
|
return ibuf;
|
|
}
|
|
|
|
|
|
int startquicktime (struct anim *anim)
|
|
{
|
|
NSAutoreleasePool *pool;
|
|
NSArray* videoTracks;
|
|
NSSize frameSize;
|
|
QTTime qtTimeDuration;
|
|
NSDictionary *attributes;
|
|
|
|
anim->qtime = MEM_callocN (sizeof(QuicktimeMovie),"animqt");
|
|
|
|
if (anim->qtime == NULL) {
|
|
if(QTIME_DEBUG) printf("Can't alloc qtime: %s\n", anim->name);
|
|
return -1;
|
|
}
|
|
|
|
pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
[QTMovie enterQTKitOnThread];
|
|
|
|
attributes = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
[NSString stringWithCString:anim->name
|
|
encoding:[NSString defaultCStringEncoding]], QTMovieFileNameAttribute,
|
|
[NSNumber numberWithBool:NO], QTMovieEditableAttribute,
|
|
nil];
|
|
|
|
anim->qtime->movie = [QTMovie movieWithAttributes:attributes error:NULL];
|
|
|
|
if (!anim->qtime->movie) {
|
|
if(QTIME_DEBUG) printf("qt: bad movie %s\n", anim->name);
|
|
MEM_freeN(anim->qtime);
|
|
if(QTIME_DEBUG) printf("qt: can't load %s\n", anim->name);
|
|
[QTMovie exitQTKitOnThread];
|
|
[pool drain];
|
|
return -1;
|
|
}
|
|
[anim->qtime->movie retain];
|
|
|
|
// sets Media and Track!
|
|
|
|
videoTracks = [anim->qtime->movie tracksOfMediaType:QTMediaTypeVideo];
|
|
|
|
if([videoTracks count] == 0) {
|
|
if(QTIME_DEBUG) printf("qt: no video tracks for movie %s\n", anim->name);
|
|
[anim->qtime->movie release];
|
|
MEM_freeN(anim->qtime);
|
|
if(QTIME_DEBUG) printf("qt: can't load %s\n", anim->name);
|
|
[QTMovie exitQTKitOnThread];
|
|
[pool drain];
|
|
return -1;
|
|
}
|
|
|
|
anim->qtime->media = [[videoTracks objectAtIndex:0] media];
|
|
[anim->qtime->media retain];
|
|
|
|
|
|
frameSize = [[anim->qtime->movie attributeForKey:QTMovieNaturalSizeAttribute] sizeValue];
|
|
anim->x = frameSize.width;
|
|
anim->y = frameSize.height;
|
|
|
|
if(anim->x == 0 && anim->y == 0) {
|
|
if(QTIME_DEBUG) printf("qt: error, no dimensions\n");
|
|
free_anim_quicktime(anim);
|
|
[pool drain];
|
|
return -1;
|
|
}
|
|
|
|
anim->qtime->ibuf = IMB_allocImBuf (anim->x, anim->y, 32, IB_rect);
|
|
|
|
qtTimeDuration = [[anim->qtime->media attributeForKey:QTMediaDurationAttribute] QTTimeValue];
|
|
anim->qtime->durationTime = qtTimeDuration.timeValue;
|
|
anim->qtime->durationScale = qtTimeDuration.timeScale;
|
|
|
|
anim->qtime->framecount = [[anim->qtime->media attributeForKey:QTMediaSampleCountAttribute] longValue];
|
|
anim->qtime->previousPosition = -2; //Force seeking for first read
|
|
|
|
//fill blender's anim struct
|
|
|
|
anim->duration = anim->qtime->framecount;
|
|
anim->params = 0;
|
|
|
|
anim->interlacing = 0;
|
|
anim->orientation = 0;
|
|
anim->framesize = anim->x * anim->y * 4;
|
|
|
|
anim->curposition = 0;
|
|
|
|
[pool drain];
|
|
|
|
return 0;
|
|
}
|
|
|
|
int imb_is_a_quicktime (char *name)
|
|
{
|
|
NSImage *image;
|
|
int result;
|
|
NSAutoreleasePool *pool;
|
|
|
|
if(!G.have_quicktime) return 0;
|
|
|
|
pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
// don't let quicktime image import handle these
|
|
if( BLI_testextensie(name, ".swf") ||
|
|
BLI_testextensie(name, ".txt") ||
|
|
BLI_testextensie(name, ".mpg") ||
|
|
BLI_testextensie(name, ".wav") ||
|
|
BLI_testextensie(name, ".mov") || // not as image, doesn't work
|
|
BLI_testextensie(name, ".avi") ||
|
|
BLI_testextensie(name, ".mp3")) return 0;
|
|
|
|
|
|
image = [[NSImage alloc] initWithContentsOfFile:[NSString stringWithUTF8String:name]];
|
|
if (image) {
|
|
[image release];
|
|
result = true;
|
|
}
|
|
else
|
|
result = false;
|
|
|
|
[pool drain];
|
|
return result;
|
|
}
|
|
|
|
ImBuf *imb_quicktime_decode(unsigned char *mem, int size, int flags)
|
|
{
|
|
struct ImBuf *ibuf = NULL;
|
|
NSSize bitmapSize;
|
|
uchar *rasterRGB = NULL;
|
|
uchar *rasterRGBA = NULL;
|
|
uchar *toIBuf = NULL;
|
|
int x, y, to_i, from_i;
|
|
NSData *data;
|
|
NSBitmapImageRep *bitmapImage;
|
|
NSBitmapImageRep *blBitmapFormatImageRGB,*blBitmapFormatImageRGBA;
|
|
NSAutoreleasePool *pool;
|
|
|
|
if(!G.have_quicktime)
|
|
return NULL;
|
|
|
|
pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
data = [NSData dataWithBytes:mem length:size];
|
|
bitmapImage = [[NSBitmapImageRep alloc] initWithData:data];
|
|
|
|
if (!bitmapImage) {
|
|
fprintf(stderr, "imb_cocoaLoadImage: error loading image\n");
|
|
[pool drain];
|
|
return NULL;
|
|
}
|
|
|
|
bitmapSize.width = [bitmapImage pixelsWide];
|
|
bitmapSize.height = [bitmapImage pixelsHigh];
|
|
|
|
/* Tell cocoa image resolution is same as current system one */
|
|
[bitmapImage setSize:bitmapSize];
|
|
|
|
/* allocate the image buffer */
|
|
ibuf = IMB_allocImBuf(bitmapSize.width, bitmapSize.height, 32/*RGBA*/, 0);
|
|
if (!ibuf) {
|
|
fprintf(stderr,
|
|
"imb_cocoaLoadImage: could not allocate memory for the image.\n");
|
|
[bitmapImage release];
|
|
[pool drain];
|
|
return NULL;
|
|
}
|
|
|
|
/* read in the image data */
|
|
if (!(flags & IB_test)) {
|
|
|
|
/* allocate memory for the ibuf->rect */
|
|
imb_addrectImBuf(ibuf);
|
|
|
|
/* Convert the image in a RGBA 32bit format */
|
|
/* As Core Graphics does not support contextes with non premutliplied alpha,
|
|
we need to get alpha key values in a separate batch */
|
|
|
|
/* First get RGB values w/o Alpha to avoid pre-multiplication, 32bit but last byte is unused */
|
|
blBitmapFormatImageRGB = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
|
|
pixelsWide:bitmapSize.width
|
|
pixelsHigh:bitmapSize.height
|
|
bitsPerSample:8 samplesPerPixel:3 hasAlpha:NO isPlanar:NO
|
|
colorSpaceName:NSCalibratedRGBColorSpace
|
|
bitmapFormat:0
|
|
bytesPerRow:4*bitmapSize.width
|
|
bitsPerPixel:32/*RGB format padded to 32bits*/];
|
|
|
|
[NSGraphicsContext saveGraphicsState];
|
|
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGB]];
|
|
[bitmapImage draw];
|
|
[NSGraphicsContext restoreGraphicsState];
|
|
|
|
rasterRGB = (uchar *)[blBitmapFormatImageRGB bitmapData];
|
|
if (rasterRGB == NULL) {
|
|
[bitmapImage release];
|
|
[blBitmapFormatImageRGB release];
|
|
[pool drain];
|
|
return NULL;
|
|
}
|
|
|
|
/* Then get Alpha values by getting the RGBA image (that is premultiplied btw) */
|
|
blBitmapFormatImageRGBA = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
|
|
pixelsWide:bitmapSize.width
|
|
pixelsHigh:bitmapSize.height
|
|
bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO
|
|
colorSpaceName:NSCalibratedRGBColorSpace
|
|
bitmapFormat:0
|
|
bytesPerRow:4*bitmapSize.width
|
|
bitsPerPixel:32/* RGBA */];
|
|
|
|
[NSGraphicsContext saveGraphicsState];
|
|
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGBA]];
|
|
[bitmapImage draw];
|
|
[NSGraphicsContext restoreGraphicsState];
|
|
|
|
rasterRGBA = (uchar *)[blBitmapFormatImageRGBA bitmapData];
|
|
if (rasterRGBA == NULL) {
|
|
[bitmapImage release];
|
|
[blBitmapFormatImageRGB release];
|
|
[blBitmapFormatImageRGBA release];
|
|
[pool drain];
|
|
return NULL;
|
|
}
|
|
|
|
/*Copy the image to ibuf, flipping it vertically*/
|
|
toIBuf = (uchar *)ibuf->rect;
|
|
for (x = 0; x < bitmapSize.width; x++) {
|
|
for (y = 0; y < bitmapSize.height; y++) {
|
|
to_i = (bitmapSize.height-y-1)*bitmapSize.width + x;
|
|
from_i = y*bitmapSize.width + x;
|
|
|
|
toIBuf[4*to_i] = rasterRGB[4*from_i]; /* R */
|
|
toIBuf[4*to_i+1] = rasterRGB[4*from_i+1]; /* G */
|
|
toIBuf[4*to_i+2] = rasterRGB[4*from_i+2]; /* B */
|
|
toIBuf[4*to_i+3] = rasterRGBA[4*from_i+3]; /* A */
|
|
}
|
|
}
|
|
|
|
[blBitmapFormatImageRGB release];
|
|
[blBitmapFormatImageRGBA release];
|
|
}
|
|
|
|
/* release the cocoa objects */
|
|
[bitmapImage release];
|
|
[pool drain];
|
|
|
|
if (ENDIAN_ORDER == B_ENDIAN) IMB_convert_rgba_to_abgr(ibuf);
|
|
|
|
/* return successfully */
|
|
return (ibuf);
|
|
}
|
|
|
|
|
|
#endif /* WITH_QUICKTIME */
|
|
|