1//
2//  SnapshotTaker.mm
3//  LDView
4//
5//  Created by Travis Cobbs on 10/14/07.
6//  Copyright 2008 Travis Cobbs. All rights reserved.
7//
8
9#import "SnapshotTaker.h"
10#include <LDLib/LDrawModelViewer.h>
11#include <TRE/TREGLExtensions.h>
12#include <TCFoundation/TCAlert.h>
13#import "SnapshotAlertHandler.h"
14#import "AutoDeleter.h"
15#include <algorithm>
16
17#define PB_WIDTH 1024
18#define PB_HEIGHT 1024
19
20@implementation SnapshotTaker
21
22- (id)init
23{
24	return [self initWithModelViewer:nil sharedContext:nil];
25}
26
27- (BOOL)choosePixelFormat:(CGLPixelFormatObj *)pPixelFormat remote:(bool)remote
28{
29START_IGNORE_DEPRECATION
30	int attrs[] =
31	{
32		kCGLPFAPBuffer,
33		kCGLPFAColorSize, 16,
34		kCGLPFAAlphaSize, 4,
35		kCGLPFADepthSize, 16,
36		kCGLPFAStencilSize, 8,
37		kCGLPFAMaximumPolicy,
38		kCGLPFAAccelerated,
39		kCGLPFANoRecovery,
40		0,	// Spot for kCGLPFARemotePBuffer if tryRemote is set
41		0
42	};
43END_IGNORE_DEPRECATION
44	GLint num;
45
46	if (remote)
47	{
48#pragma clang diagnostic push
49#pragma clang diagnostic ignored "-Wdeprecated-declarations"
50		attrs[sizeof(attrs) / sizeof(attrs[0]) - 2] = kCGLPFARemotePBuffer;
51#pragma clang diagnostic pop
52	}
53	if (CGLChoosePixelFormat((CGLPixelFormatAttribute *)attrs, pPixelFormat, &num) == kCGLNoError)
54	{
55		return YES;
56	}
57	return NO;
58}
59
60- (id)initWithModelViewer:(LDrawModelViewer *)theModelViewer sharedContext:(NSOpenGLContext *)theSharedContext
61{
62	self = [super init];
63	if (self)
64	{
65		sharedContext = theSharedContext;
66		modelViewer = theModelViewer;
67		snapshotAlertHandler = new SnapshotAlertHandler(self);
68		if (modelViewer)
69		{
70			ldSnapshotTaker = new LDSnapshotTaker(modelViewer);
71			if (TREGLExtensions::haveFramebufferObjectExtension())
72			{
73				ldSnapshotTaker->setUseFBO(true);
74			}
75		}
76	}
77	return self;
78}
79
80- (void)contextCreatedWithPixelFormat:(CGLPixelFormatObj)pixelFormat
81{
82	GLint virtualScreen;
83
84	CGLDestroyPixelFormat(pixelFormat);
85	CGLSetCurrentContext(context);
86	CGLGetVirtualScreen(context, &virtualScreen);
87START_IGNORE_DEPRECATION
88	CGLSetPBuffer(context, pbuffer, 0, 0, virtualScreen);
89END_IGNORE_DEPRECATION
90}
91
92- (BOOL)useFBO
93{
94	return ldSnapshotTaker != NULL && ldSnapshotTaker->getUseFBO();
95}
96
97- (void)setupContext
98{
99	if ([self useFBO])
100	{
101		return;
102	}
103START_IGNORE_DEPRECATION
104	CGLError result = CGLCreatePBuffer(PB_WIDTH, PB_HEIGHT, GL_TEXTURE_2D, GL_RGB, 0, &pbuffer);
105END_IGNORE_DEPRECATION
106	if (result == kCGLNoError)
107	{
108		CGLPixelFormatObj pixelFormat;
109
110		if ([self choosePixelFormat:&pixelFormat remote:sharedContext == nil])
111		{
112			if (CGLCreateContext(pixelFormat, (CGLContextObj)[sharedContext CGLContextObj], &context) == kCGLNoError)
113			{
114				[self contextCreatedWithPixelFormat:pixelFormat];
115				return;
116			}
117			CGLDestroyPixelFormat(pixelFormat);
118		}
119		if (sharedContext == nil)
120		{
121			NSLog(@"Error creating remote OpenGL context; trying non-remote context.\n");
122		}
123		if ([self choosePixelFormat:&pixelFormat remote:false])
124		{
125			if (CGLCreateContext(pixelFormat, (CGLContextObj)[sharedContext CGLContextObj], &context) == kCGLNoError)
126			{
127				[self contextCreatedWithPixelFormat:pixelFormat];
128				return;
129			}
130			CGLDestroyPixelFormat(pixelFormat);
131		}
132		if (sharedContext == nil)
133		{
134			NSLog(@"Error creating OpenGL context for snapshot.\n");
135		}
136		else
137		{
138			NSRunAlertPanel(@"Error", @"Error creating OpenGL context for snapshot.", @"OK", nil, nil);
139		}
140	}
141}
142
143- (void)dealloc
144{
145	TCObject::release(snapshotAlertHandler);
146	TCObject::release(ldSnapshotTaker);
147	if (context)
148	{
149		CGLDestroyContext(context);
150	}
151	if (pbuffer)
152	{
153START_IGNORE_DEPRECATION
154		CGLDestroyPBuffer(pbuffer);
155END_IGNORE_DEPRECATION
156	}
157	[super dealloc];
158}
159
160- (void)setImageType:(LDSnapshotTaker::ImageType)value
161{
162	ldSnapshotTaker->setImageType(value);
163}
164
165- (void)setTrySaveAlpha:(bool)value
166{
167	ldSnapshotTaker->setTrySaveAlpha(value);
168}
169
170- (void)setAutoCrop:(bool)value
171{
172	ldSnapshotTaker->setAutoCrop(value);
173}
174
175- (void)saveFileSetup
176{
177	if (![self useFBO])
178	{
179		CGLSetCurrentContext(context);
180		glViewport(0, 0, PB_WIDTH, PB_HEIGHT);
181		if (modelViewer)
182		{
183			modelViewer->perspectiveView();
184		}
185		glViewport(0, 0, PB_WIDTH, PB_HEIGHT);
186		glDepthFunc(GL_LEQUAL);
187		glEnable(GL_DEPTH_TEST);
188		glDrawBuffer(GL_FRONT);
189		glReadBuffer(GL_FRONT);
190	}
191	if (modelViewer)
192	{
193		if (modelViewer->getMainTREModel() == NULL && !modelViewer->getNeedsReload())
194		{
195			modelViewer->loadModel(true);
196		}
197	}
198}
199
200- (LDSnapshotTaker *)ldSnapshotTaker
201{
202	return ldSnapshotTaker;
203}
204
205- (void)snapshotCallback:(TCAlert *)alert;
206{
207	if ([self useFBO])
208	{
209		return;
210	}
211	if (strcmp(alert->getMessage(), "PreSave") == 0)
212	{
213		if (!context)
214		{
215			[self setupContext];
216		}
217		[self saveFileSetup];
218	}
219	else if (strcmp(alert->getMessage(), "PreFbo") == 0)
220	{
221		CGLPixelFormatObj pixelFormat;
222		if ([self choosePixelFormat:&pixelFormat remote:NO])
223		{
224			if (CGLCreateContext(pixelFormat, NULL, &context) == kCGLNoError)
225			{
226				CGLSetCurrentContext(context);
227				TREGLExtensions::setup();
228				ldSnapshotTaker = (LDSnapshotTaker*)alert->getSender()->retain();
229				ldSnapshotTaker->setUseFBO(true);
230			}
231			CGLDestroyPixelFormat(pixelFormat);
232		}
233	}
234}
235
236- (bool)saveFile
237{
238	if (ldSnapshotTaker)
239	{
240		return ldSnapshotTaker->saveImage();
241	}
242	else
243	{
244		bool tried;
245		LDSnapshotTaker::doCommandLine(true, true, &tried);
246		return tried;
247	}
248}
249
250- (NSImage *)imageWithWidth:(int)width height:(int)height zoomToFit:(bool)zoomToFit
251{
252	int actualWidth = width;
253	int actualHeight = height;
254
255	ldSnapshotTaker->setTrySaveAlpha(false);
256	TCByte *imageData = ldSnapshotTaker->grabImage(actualWidth, actualHeight, zoomToFit, NULL, NULL);
257	if (imageData)
258	{
259		int rowSize = TCImage::roundUp(actualWidth * 3, 4);
260		TCByte *imageDataArray[5] = { imageData, NULL, NULL, NULL, NULL };
261		NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:imageDataArray pixelsWide:actualWidth pixelsHigh:actualHeight bitsPerSample:8 samplesPerPixel:3 hasAlpha:NO isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace bytesPerRow:rowSize bitsPerPixel:24];
262		NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize((float)actualWidth, (float)actualHeight)];
263		int y;
264		TCByte *tmpRow = new TCByte[rowSize];
265
266		// The good news is that drawing this image to the printer doesn't
267		// copy the image data.  The bad news is that means that we can't free
268		// the image data until we get back up to the main autorelease pool,
269		// so create an AutoDeleter class that will free the memory when it
270		// gets released during processing of the main autorelease pool.
271		[AutoDeleter autoDeleterWithBytePointer:imageData];
272		// Flip the image
273		for (y = 0; y < actualHeight / 2; y++)
274		{
275			TCByte *botRow = &imageData[y * rowSize];
276			TCByte *topRow = &imageData[(actualHeight - y - 1) * rowSize];
277
278			memcpy(tmpRow, botRow, rowSize);
279			memcpy(botRow, topRow, rowSize);
280			memcpy(topRow, tmpRow, rowSize);
281		}
282		delete[] tmpRow;
283		[image addRepresentation:imageRep];
284		[imageRep release];
285		return [image autorelease];
286	}
287	return nil;
288}
289
290- (bool)saveFile:(NSString *)filename width:(int)width height:(int)height zoomToFit:(bool)zoomToFit
291{
292	[self saveFileSetup];
293	return ldSnapshotTaker->saveImage([filename UTF8String], width, height, zoomToFit);
294}
295
296@end
297