1/*
2    PPDocument_Pasteboard.m
3
4    Copyright 2013-2018 Josh Freeman
5    http://www.twilightedge.com
6
7    This file is part of PikoPixel for Mac OS X and GNUstep.
8    PikoPixel is a graphical application for drawing & editing pixel-art images.
9
10    PikoPixel is free software: you can redistribute it and/or modify it under
11    the terms of the GNU Affero General Public License as published by the
12    Free Software Foundation, either version 3 of the License, or (at your
13    option) any later version approved for PikoPixel by its copyright holder (or
14    an authorized proxy).
15
16    PikoPixel is distributed in the hope that it will be useful, but WITHOUT ANY
17    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18    FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19    details.
20
21    You should have received a copy of the GNU Affero General Public License
22    along with this program. If not, see <http://www.gnu.org/licenses/>.
23*/
24
25#import "PPDocument.h"
26
27#import "PPGeometry.h"
28#import "PPDocumentLayer.h"
29#import "NSBitmapImageRep_PPUtilities.h"
30#import "NSPasteboard_PPUtilities.h"
31
32
33@interface PPDocument (PasteboardPrivateMethods)
34
35- (bool) getPasteboardImageBitmap: (NSBitmapImageRep **) returnedImageBitmap
36            maskBitmap: (NSBitmapImageRep **) returnedMaskBitmap
37            boundsOnCanvas: (NSRect *) returnedBoundsOnCanvas
38            opacity: (float *) returnedOpacity;
39
40- (bool) cropImageBitmapToCanvasFrame: (NSBitmapImageRep **) inOutImageBitmap
41            withMaskBitmap: (NSBitmapImageRep **) inOutMaskBitmap
42            andAdjustBoundsOnCanvas: (NSRect *) inOutBoundsOnCanvas;
43
44@end
45
46@implementation PPDocument (Pasteboard)
47
48- (bool) canReadFromPasteboard
49{
50    return [NSPasteboard ppPasteboardHasBitmap];
51}
52
53- (bool) canWriteToPasteboard
54{
55    return _hasSelection;
56}
57
58- (void) copySelectionToPasteboardFromTarget: (PPLayerOperationTarget) operationTarget
59{
60    NSBitmapImageRep *targetBitmap, *croppedTargetBitmap, *croppedMask;
61    float selectionOpacity;
62
63    if (!_hasSelection)
64        goto ERROR;
65
66    if (operationTarget == kPPLayerOperationTarget_DrawingLayerOnly)
67    {
68        targetBitmap = _drawingLayerBitmap;
69        selectionOpacity = [_drawingLayer opacity];
70    }
71    else
72    {
73        targetBitmap = _mergedVisibleLayersBitmap;
74        selectionOpacity = 1.0f;
75    }
76
77    croppedTargetBitmap = [targetBitmap ppBitmapCroppedToBounds: _selectionBounds];
78    croppedMask = [_selectionMask ppBitmapCroppedToBounds: _selectionBounds];
79
80    if (!croppedTargetBitmap || !croppedMask)
81    {
82        goto ERROR;
83    }
84
85    [NSPasteboard ppSetImageBitmap: croppedTargetBitmap
86                    maskBitmap: croppedMask
87                    bitmapOrigin: _selectionBounds.origin
88                    canvasSize: _canvasFrame.size
89                    andOpacity: selectionOpacity];
90
91    return;
92
93ERROR:
94    return;
95}
96
97- (void) cutSelectionToPasteboardFromTarget: (PPLayerOperationTarget) operationTarget
98{
99    [self copySelectionToPasteboardFromTarget: operationTarget];
100
101    [self noninteractiveEraseSelectedAreaInTarget: operationTarget
102            andClearSelectionMask: YES];
103}
104
105- (void) pasteNewLayerFromPasteboard
106{
107    NSBitmapImageRep *pasteboardImageBitmap;
108    NSRect pasteboardBoundsOnCanvas;
109    float pasteboardOpacity;
110    PPDocumentLayer *layer;
111
112    if (![self getPasteboardImageBitmap: &pasteboardImageBitmap
113                maskBitmap: NULL
114                boundsOnCanvas: &pasteboardBoundsOnCanvas
115                opacity: &pasteboardOpacity])
116    {
117        goto ERROR;
118    }
119
120    layer = [PPDocumentLayer layerWithSize: _canvasFrame.size andName: @"Pasted Layer"];
121
122    if (!layer)
123        return;
124
125    [[layer bitmap] ppCopyFromBitmap: pasteboardImageBitmap
126                    toPoint: pasteboardBoundsOnCanvas.origin];
127
128    [layer handleUpdateToBitmapInRect: pasteboardBoundsOnCanvas];
129
130    [layer setOpacity: pasteboardOpacity];
131
132    [self insertLayer: layer atIndex: _indexOfDrawingLayer + 1 andSetAsDrawingLayer: YES];
133
134    [[self undoManager] setActionName: @"Paste as New Layer"];
135
136    return;
137
138ERROR:
139    return;
140}
141
142- (void) pasteIntoDrawingLayerFromPasteboard
143{
144    NSBitmapImageRep *pasteboardImageBitmap, *pasteboardMaskBitmap, *updateBitmap;
145    NSRect pasteboardBoundsOnCanvas;
146
147    if (![self getPasteboardImageBitmap: &pasteboardImageBitmap
148                maskBitmap: &pasteboardMaskBitmap
149                boundsOnCanvas: &pasteboardBoundsOnCanvas
150                opacity: NULL])
151    {
152        goto ERROR;
153    }
154
155    // returned pasteboardMaskBitmap can be nil
156    if (!pasteboardMaskBitmap)
157    {
158        pasteboardMaskBitmap =
159                        [pasteboardImageBitmap ppMaskBitmapForVisiblePixelsInImageBitmap];
160
161        if (!pasteboardMaskBitmap)
162            goto ERROR;
163    }
164
165    updateBitmap = [_drawingLayerBitmap ppBitmapCroppedToBounds: pasteboardBoundsOnCanvas];
166
167    if (!updateBitmap)
168        goto ERROR;
169
170    [updateBitmap ppMaskedCopyFromImageBitmap: pasteboardImageBitmap
171                    usingMask: pasteboardMaskBitmap];
172
173    [self copyImageBitmapToDrawingLayer: updateBitmap atPoint: pasteboardBoundsOnCanvas.origin];
174
175    if (_hasSelection)
176    {
177        [self deselectAll];
178    }
179
180    [self setSelectionMaskAreaWithBitmap: pasteboardMaskBitmap
181            atPoint: pasteboardBoundsOnCanvas.origin];
182
183    [[self undoManager] setActionName: @"Paste into Draw Layer"];
184
185    return;
186
187ERROR:
188    return;
189}
190
191+ (PPDocument *) ppDocumentFromPasteboard
192{
193    NSBitmapImageRep *imageBitmap, *maskBitmap;
194    float opacity;
195    PPDocumentLayer *layer;
196    NSArray *layersArray;
197    PPDocument *ppDocument;
198
199    if (![NSPasteboard ppGetImageBitmap: &imageBitmap
200                        maskBitmap: &maskBitmap
201                        bitmapOrigin: NULL
202                        canvasSize: NULL
203                        andOpacity: &opacity])
204    {
205        goto ERROR;
206    }
207
208    layer = [[[PPDocumentLayer alloc] initWithSize: [imageBitmap ppSizeInPixels]
209                                        name: @"Main Layer"
210                                        tiffData: [imageBitmap TIFFRepresentation]
211                                        opacity: opacity
212                                        isEnabled: YES]
213                                autorelease];
214
215    if (!layer)
216        goto ERROR;
217
218    layersArray = [NSArray arrayWithObject: layer];
219
220    if (!layersArray)
221        goto ERROR;
222
223    ppDocument = [[[PPDocument alloc] init] autorelease];
224
225    if (!ppDocument)
226        goto ERROR;
227
228    [ppDocument setLayers: layersArray];
229
230    if (maskBitmap)
231    {
232        [ppDocument setSelectionMask: maskBitmap];
233    }
234
235    [[ppDocument undoManager] removeAllActions];
236
237    return ppDocument;
238
239ERROR:
240    return nil;
241}
242
243#pragma mark Private methods
244
245- (bool) getPasteboardImageBitmap: (NSBitmapImageRep **) returnedImageBitmap
246            maskBitmap: (NSBitmapImageRep **) returnedMaskBitmap
247            boundsOnCanvas: (NSRect *) returnedBoundsOnCanvas
248            opacity: (float *) returnedOpacity
249{
250    NSBitmapImageRep *imageBitmap;
251    NSPoint bitmapOrigin;
252    NSSize bitmapCanvasSize;
253
254    if (![NSPasteboard ppGetImageBitmap: &imageBitmap
255                        maskBitmap: returnedMaskBitmap
256                        bitmapOrigin: &bitmapOrigin
257                        canvasSize: &bitmapCanvasSize
258                        andOpacity: returnedOpacity])
259    {
260        goto ERROR;
261    }
262
263    if (returnedBoundsOnCanvas)
264    {
265        NSRect boundsOnCanvas;
266
267        boundsOnCanvas.origin = bitmapOrigin;
268        boundsOnCanvas.size = [imageBitmap ppSizeInPixels];
269
270        if (!NSEqualSizes(bitmapCanvasSize, _canvasFrame.size))
271        {
272            boundsOnCanvas = PPGeometry_CenterRectInRect(boundsOnCanvas, _canvasFrame);
273        }
274
275        if (![self cropImageBitmapToCanvasFrame: &imageBitmap
276                    withMaskBitmap: returnedMaskBitmap
277                    andAdjustBoundsOnCanvas: &boundsOnCanvas])
278        {
279            goto ERROR;
280        }
281
282        *returnedBoundsOnCanvas = boundsOnCanvas;
283    }
284
285    if (returnedImageBitmap)
286    {
287        *returnedImageBitmap = imageBitmap;
288    }
289
290    return YES;
291
292ERROR:
293    return NO;
294}
295
296- (bool) cropImageBitmapToCanvasFrame: (NSBitmapImageRep **) inOutImageBitmap
297            withMaskBitmap: (NSBitmapImageRep **) inOutMaskBitmap
298            andAdjustBoundsOnCanvas: (NSRect *) inOutBoundsOnCanvas
299{
300    NSBitmapImageRep *imageBitmap, *newImageBitmap;
301    NSRect boundsOnCanvas, newBoundsOnCanvas, croppingBounds;
302
303    if (!inOutImageBitmap || !inOutBoundsOnCanvas)
304    {
305        goto ERROR;
306    }
307
308    imageBitmap = *inOutImageBitmap;
309    boundsOnCanvas = *inOutBoundsOnCanvas;
310
311    if (!imageBitmap || NSIsEmptyRect(boundsOnCanvas))
312    {
313        goto ERROR;
314    }
315
316    if (NSContainsRect(_canvasFrame, boundsOnCanvas))
317    {
318        return YES;
319    }
320
321    newBoundsOnCanvas = NSIntersectionRect(_canvasFrame, boundsOnCanvas);
322
323    if (NSIsEmptyRect(newBoundsOnCanvas))
324    {
325        goto ERROR;
326    }
327
328    croppingBounds.size = newBoundsOnCanvas.size;
329    croppingBounds.origin =
330                PPGeometry_PointDifference(newBoundsOnCanvas.origin, boundsOnCanvas.origin);
331
332    newImageBitmap = [imageBitmap ppBitmapCroppedToBounds: croppingBounds];
333
334    if (!newImageBitmap)
335        goto ERROR;
336
337    if (inOutMaskBitmap)
338    {
339        NSBitmapImageRep *maskBitmap = *inOutMaskBitmap;
340
341        if (maskBitmap)
342        {
343            maskBitmap = [maskBitmap ppBitmapCroppedToBounds: croppingBounds];
344
345            if (!maskBitmap)
346                goto ERROR;
347
348            *inOutMaskBitmap = maskBitmap;
349        }
350    }
351
352    *inOutImageBitmap = newImageBitmap;
353    *inOutBoundsOnCanvas = newBoundsOnCanvas;
354
355    return YES;
356
357ERROR:
358    return NO;
359}
360
361@end
362