]> git.sesse.net Git - vlc/blob - modules/video_output/coregraphicslayer.m
glwin32: remove unneeded includes and defines
[vlc] / modules / video_output / coregraphicslayer.m
1 /*****************************************************************************
2  * coregraphicslayer.m: CoreGraphics video output for NPAPI plugins
3  *****************************************************************************
4  * Copyright (C) 2013 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Felix Paul Kühne <fkuehne # videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #ifdef HAVE_CONFIG_H
29 # import "config.h"
30 #endif
31
32 #import <vlc_common.h>
33 #import <vlc_plugin.h>
34 #import <vlc_vout_display.h>
35 #import <vlc_picture_pool.h>
36
37 #import <QuartzCore/QuartzCore.h>
38 #import <AppKit/AppKit.h>
39
40 static int Open(vlc_object_t *);
41 static void Close(vlc_object_t *);
42
43 vlc_module_begin ()
44     set_description(N_("CoreGraphics video output"))
45     set_shortname("CoreGraphics")
46
47     set_category(CAT_VIDEO)
48     set_subcategory(SUBCAT_VIDEO_VOUT)
49     set_capability("vout display", 0)
50
51     set_callbacks(Open, Close)
52 vlc_module_end ()
53
54 @protocol VLCCoreGraphicsVideoLayerEmbedding <NSObject>
55 - (void)addVoutLayer:(CALayer *)aLayer;
56 - (void)removeVoutLayer:(CALayer *)aLayer;
57 - (CGSize)currentOutputSize;
58 @end
59
60 @interface VLCCoreGraphicsLayer : CALayer {
61     CGImageRef _lastFrame;
62     bool lock;
63     vout_display_t *_vd;
64 }
65 @property (nonatomic) vout_display_t *vd;
66 - (void)setLastFrame:(CGImageRef)lastFrame;
67 @end
68
69 /*****************************************************************************
70  * Local prototypes
71  *****************************************************************************/
72 struct vout_display_sys_t {
73     picture_pool_t *pool;
74     picture_resource_t resource;
75
76     VLCCoreGraphicsLayer *cgLayer;
77
78     vout_window_t *embed;
79     bool has_first_frame;
80
81     size_t componentsPerPixel;
82     size_t bitsPerComponent;
83     size_t bitsPerPixel;
84
85     CALayer <VLCCoreGraphicsVideoLayerEmbedding> *container;
86     CGColorSpaceRef colorspace;
87 };
88 static picture_pool_t *Pool(vout_display_t *, unsigned);
89 static void Display(vout_display_t *, picture_t *, subpicture_t *);
90 static int Control(vout_display_t *, int, va_list);
91
92 /*****************************************************************************
93  * OpenVideo: activates dummy vout display method
94  *****************************************************************************/
95 static int Open(vlc_object_t *object)
96 {
97     vout_display_t *vd = (vout_display_t *)object;
98     vout_display_sys_t *sys = calloc(1, sizeof(*sys));
99     NSAutoreleasePool *nsPool = nil;
100
101     if (!sys)
102         return VLC_ENOMEM;
103     sys->pool = NULL;
104
105     id container = var_CreateGetAddress(vd, "drawable-nsobject");
106     if (container)
107         vout_display_DeleteWindow(vd, NULL);
108     else {
109         vout_window_cfg_t wnd_cfg;
110
111         memset(&wnd_cfg, 0, sizeof(wnd_cfg));
112         wnd_cfg.type = VOUT_WINDOW_TYPE_NSOBJECT;
113         wnd_cfg.x = var_InheritInteger(vd, "video-x");
114         wnd_cfg.y = var_InheritInteger(vd, "video-y");
115         wnd_cfg.height = vd->cfg->display.height;
116         wnd_cfg.width = vd->cfg->display.width;
117
118         sys->embed = vout_display_NewWindow(vd, &wnd_cfg);
119         if (sys->embed)
120             container = sys->embed->handle.nsobject;
121
122         if (!container) {
123             msg_Err(vd, "No drawable-nsobject found!");
124             goto bailout;
125         }
126     }
127
128     /* store for later, released in Close() */
129     sys->container = [container retain];
130
131     /* allocate pool */
132     nsPool = [[NSAutoreleasePool alloc] init];
133
134     if ([container respondsToSelector:@selector(addVoutLayer:)]) {
135         msg_Dbg(vd, "container implements implicit protocol");
136         sys->cgLayer = [[VLCCoreGraphicsLayer alloc] init];
137         [container addVoutLayer:sys->cgLayer];
138     } else if ([container respondsToSelector:@selector(addSublayer:)] || [container isKindOfClass:[CALayer class]]) {
139         msg_Dbg(vd, "container doesn't implement implicit protocol, fallback mode used");
140         sys->cgLayer = [[VLCCoreGraphicsLayer alloc] init];
141         [container addSublayer:sys->cgLayer];
142     } else {
143         msg_Err(vd, "Provided NSObject container isn't compatible");
144         goto bailout;
145     }
146     [sys->cgLayer setVd:vd];
147
148     /* setup output format */
149     video_format_t fmt = vd->fmt;
150
151     char *chroma = "RGBA";
152     fmt.i_chroma = vlc_fourcc_GetCodecFromString(VIDEO_ES, chroma);
153     msg_Dbg(vd, "forcing chroma 0x%.8x (%4.4s)", fmt.i_chroma, (char*)&fmt.i_chroma);
154
155     video_format_FixRgb(&fmt);
156     msg_Dbg(vd, "will use pixel format %4.4s", (char*)&fmt.i_chroma);
157
158     /* setup vout display */
159     vout_display_info_t info = vd->info;
160     info.has_hide_mouse = false;
161     vd->sys = sys;
162     vd->fmt = fmt;
163     vd->info = info;
164     vd->pool    = Pool;
165     vd->display = Display;
166     vd->control = Control;
167     vd->prepare = NULL;
168     vd->manage  = NULL;
169
170     /* setup initial state */
171     CGSize outputSize;
172     if ([container respondsToSelector:@selector(currentOutputSize)])
173         outputSize = [container currentOutputSize];
174     else
175         outputSize = [sys->container visibleRect].size;
176     vout_display_SendEventFullscreen(vd, false);
177     vout_display_SendEventDisplaySize(vd, (int)outputSize.width, (int)outputSize.height, false);
178
179     sys->colorspace = CGColorSpaceCreateDeviceRGB();
180     sys->componentsPerPixel = 4;
181     sys->bitsPerComponent = sizeof(unsigned char) * 8;
182     sys->bitsPerPixel = sys->bitsPerComponent * sys->componentsPerPixel;
183
184     return VLC_SUCCESS;
185
186 bailout:
187     if (nsPool)
188         [nsPool release];
189
190     return VLC_EGENERIC;
191 }
192
193 static void Close(vlc_object_t *object)
194 {
195     vout_display_t *vd = (vout_display_t *)object;
196     vout_display_sys_t *sys = vd->sys;
197
198     if (sys->pool)
199         picture_pool_Delete(sys->pool);
200
201     if (sys->cgLayer) {
202         if ([sys->container respondsToSelector:@selector(removeVoutLayer:)])
203             [sys->container removeVoutLayer:sys->cgLayer];
204         else
205             [sys->cgLayer removeFromSuperlayer];
206         [sys->cgLayer release];
207     }
208
209     if (sys->container)
210         [sys->container release];
211
212     free(sys);
213 }
214
215 static picture_pool_t *Pool(vout_display_t *vd, unsigned count)
216 {
217     vout_display_sys_t *sys = vd->sys;
218     if (!sys->pool)
219         sys->pool = picture_pool_NewFromFormat(&vd->fmt, count);
220     return sys->pool;
221 }
222
223 static void Display(vout_display_t *vd, picture_t *picture, subpicture_t *subpicture)
224 {
225     vout_display_sys_t *sys = vd->sys;
226
227     if (!sys->cgLayer) {
228         msg_Warn(vd, "no cglayer to Display in");
229         return;
230     }
231     uint32_t sourceWidth, sourceHeight;
232
233     sourceWidth = picture->p[0].i_visible_pitch / picture->p[0].i_pixel_pitch;
234     sourceHeight = picture->p[0].i_visible_lines;
235
236     const int crop_offset = vd->source.i_y_offset * picture->p->i_pitch +
237     vd->source.i_x_offset * picture->p->i_pixel_pitch;
238
239     CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
240                                                     &picture->p->p_pixels[crop_offset],
241                                                     sizeof(picture->p->p_pixels[crop_offset]),
242                                                     kCFAllocatorNull);
243     CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData(dataRef);
244
245     CGImageRef newFrame = CGImageCreate(sourceWidth,
246                                         sourceHeight,
247                                         sys->bitsPerComponent,
248                                         sys->bitsPerPixel,
249                                         sys->componentsPerPixel * sourceWidth,
250                                         sys->colorspace,
251                                         kCGBitmapByteOrder16Big,
252                                         dataProvider,
253                                         NULL,
254                                         true,
255                                         kCGRenderingIntentPerceptual);
256
257     CGDataProviderRelease(dataProvider);
258
259     if (!newFrame)
260         goto end;
261
262     [sys->cgLayer setLastFrame:newFrame];
263
264 end:
265     VLC_UNUSED(subpicture);
266     picture_Release(picture);
267 }
268
269 static int Control(vout_display_t *vd, int query, va_list args)
270 {
271     VLC_UNUSED(vd);
272     VLC_UNUSED(query);
273     VLC_UNUSED(args);
274     return VLC_SUCCESS;
275 }
276
277 @implementation VLCCoreGraphicsLayer
278 @synthesize vd=_vd;
279
280 - (id)init
281 {
282     if (self = [super init]) {
283         [CATransaction begin];
284         self.needsDisplayOnBoundsChange = YES;
285         self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
286         [CATransaction commit];
287     }
288
289     return self;
290 }
291
292 - (bool)locked
293 {
294     return lock;
295 }
296
297 - (void)setLastFrame:(CGImageRef)lastFrame
298 {
299     if (lock) {
300         /* drop frame since we currently drawing */
301         CGImageRelease(lastFrame);
302         return;
303     }
304
305     _lastFrame = CGImageCreateCopy(lastFrame);
306     CGImageRelease(lastFrame);
307     CGRect invalidRect = CGRectMake(0, 0, CGImageGetWidth(_lastFrame), CGImageGetHeight(_lastFrame));
308     [CATransaction begin];
309     [self setNeedsDisplayInRect:invalidRect];
310     [CATransaction commit];
311 }
312
313 - (void)drawInContext:(CGContextRef)cgContext
314 {
315     if (!cgContext)
316         return;
317
318     if (!_lastFrame)
319         return;
320
321     if (lock)
322         return;
323
324     lock = YES;
325
326     CGRect layerRect = self.bounds;
327     float display_width = 0.;
328     float display_height = 0.;
329     float media_width = CGImageGetWidth(_lastFrame);
330     float media_height = CGImageGetHeight(_lastFrame);
331
332     float src_aspect = (float)media_width / media_height;
333     float dst_aspect = (float)layerRect.size.width/layerRect.size.height;
334     if (src_aspect > dst_aspect) {
335         if (layerRect.size.width != media_width) { //don't scale if size equal
336             display_width = layerRect.size.width;
337             display_height = display_width / src_aspect; // + 0.5);
338         } else {
339             display_width = media_width;
340             display_height = media_height;
341         }
342     } else {
343         if (layerRect.size.height != media_height) { //don't scale if size equal
344             display_height = layerRect.size.height;
345             display_width = display_height * src_aspect; // + 0.5);
346         } else {
347             display_width = media_width;
348             display_height = media_height;
349         }
350     }
351
352     /* Compute the position of the video */
353     float left = (layerRect.size.width  - display_width)  / 2.;
354     float top  = (layerRect.size.height - display_height) / 2.;
355
356     CGContextSaveGState(cgContext);
357
358     // draw a clear background
359     CGContextClearRect(cgContext, CGRectMake(0., 0., layerRect.size.width, layerRect.size.height));
360
361     // draw the image
362     CGRect rect = CGRectMake(left, top, display_width, display_height);
363     CGContextDrawImage(cgContext, rect, _lastFrame);
364     lock = NO;
365
366     CGContextRestoreGState(cgContext);
367 }
368
369 - (void)resizeWithOldSuperlayerSize:(CGSize)size
370 {
371     [super resizeWithOldSuperlayerSize:size];
372
373     if (!_vd)
374         return;
375
376     CGSize outputSize = [_vd->sys->container currentOutputSize];
377
378     vout_display_place_t place;
379     vout_display_cfg_t cfg_tmp = *(_vd->cfg);
380     cfg_tmp.display.width  = outputSize.width;
381     cfg_tmp.display.height = outputSize.height;
382
383     vout_display_PlacePicture (&place, &_vd->source, &cfg_tmp, false);
384     vout_display_SendEventDisplaySize (_vd, outputSize.width, outputSize.height, _vd->cfg->is_fullscreen);
385 }
386
387 @end