1/*
2    PPDocumentLayer.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 "PPDocumentLayer.h"
26
27#import "NSImage_PPUtilities.h"
28#import "NSBitmapImageRep_PPUtilities.h"
29#import "PPGeometry.h"
30#import "PPDefines.h"
31
32
33#define kDrawingLayerCodingKey_Size         @"Size"
34#define kDrawingLayerCodingKey_Name         @"Name"
35#define kDrawingLayerCodingKey_TIFFData     @"TIFFData"
36#define kDrawingLayerCodingKey_Opacity      @"Opacity"
37#define kDrawingLayerCodingKey_IsEnabled    @"IsEnabled"
38
39#define kOpacityStepSize                    0.1f
40
41
42@interface PPDocumentLayer (PrivateMethods)
43
44- (void) setOpacity: (float) opacity andRegisterUndo: (bool) shouldRegisterUndo;
45
46- (void) notifyDelegateDidChangeNameFromOldValue: (NSString *) oldName;
47- (void) notifyDelegateDidChangeEnabledFlagFromOldValue: (bool) oldEnabledFlag;
48- (void) notifyDelegateDidChangeOpacityAndShouldRegisterUndo: (bool) shouldRegisterUndo;
49
50@end
51
52@implementation PPDocumentLayer
53
54+ layerWithSize: (NSSize) size
55        andName: (NSString *) name
56{
57    return [[[self alloc] initWithSize: size
58                            name: name
59                            tiffData: nil
60                            opacity: 1.0f
61                            isEnabled: YES]
62                    autorelease];
63}
64
65+ layerWithSize: (NSSize) size
66        name: (NSString *) name
67        tiffData: (NSData *) tiffData
68{
69    return [[[self alloc] initWithSize: size
70                            name: name
71                            tiffData: tiffData
72                            opacity: 1.0f
73                            isEnabled: YES]
74                    autorelease];
75}
76
77- initWithSize: (NSSize) size
78    name: (NSString *) name
79    tiffData: (NSData *) tiffData
80    opacity: (float) opacity
81    isEnabled: (bool) isEnabled
82{
83    self = [super init];
84
85    if (!self)
86        goto ERROR;
87
88    if (![name length])
89    {
90        goto ERROR;
91    }
92
93    size = PPGeometry_SizeClippedToIntegerValues(size);
94
95    if ((size.width < kMinCanvasDimension)
96        || (size.width > kMaxCanvasDimension)
97        || (size.height < kMinCanvasDimension)
98        || (size.height > kMaxCanvasDimension))
99    {
100        goto ERROR;
101    }
102
103    _size = size;
104
105    _name = [name copy];
106    _bitmap = [[NSBitmapImageRep ppImageBitmapOfSize: size] retain];
107    _image = [[NSImage ppImageWithBitmap: _bitmap] retain];
108
109    if (!_name || !_bitmap || !_image)
110    {
111        goto ERROR;
112    }
113
114    if (tiffData)
115    {
116        [_bitmap ppCenteredCopyFromBitmap:
117                                [NSBitmapImageRep ppImageBitmapWithImportedData: tiffData]];
118    }
119
120    if (opacity > 1.0f)
121    {
122        opacity = 1.0f;
123    }
124    else if (opacity < 0.0f)
125    {
126        opacity = 0.0f;
127    }
128
129    _opacity = _lastOpacity = opacity;
130    _isEnabled = (isEnabled) ? YES : NO;
131
132    return self;
133
134ERROR:
135    [self release];
136
137    return nil;
138}
139
140- init
141{
142    return [self initWithSize: NSZeroSize name: nil tiffData: nil opacity: 1.0f isEnabled: YES];
143}
144
145- (void) dealloc
146{
147    [_name release];
148    [_bitmap release];
149    [_image release];
150
151    [_linearBlendingBitmap release];
152
153    [super dealloc];
154}
155
156- (NSBitmapImageRep *) bitmap
157{
158    return _bitmap;
159}
160
161- (NSImage *) image
162{
163    return _image;
164}
165
166// handleUpdateToBitmapInRect: method is a patch target on GNUstep
167// (PPGNUstepGlue_ImageRecacheSpeedups)
168
169- (void) handleUpdateToBitmapInRect: (NSRect) updateRect
170{
171    [_image recache];
172
173    if (_linearBlendingBitmap)
174    {
175        [_linearBlendingBitmap ppLinearCopyFromImageBitmap: _bitmap inBounds: updateRect];
176    }
177}
178
179- (bool) isEnabled
180{
181    return _isEnabled;
182}
183
184- (void) setEnabled: (bool) enabled
185{
186    if (enabled == _isEnabled)
187    {
188        return;
189    }
190
191    _isEnabled = (enabled) ? YES : NO;
192
193    [self notifyDelegateDidChangeEnabledFlagFromOldValue: enabled ? NO : YES];
194}
195
196- (NSString *) name
197{
198    return _name;
199}
200
201- (void) setName: (NSString *) name
202{
203    NSString *oldName;
204
205    name = [[name copy] autorelease];
206
207    if (!name)
208    {
209        name = @"";
210    }
211
212    if ((_name == name) || [_name isEqualToString: name])
213    {
214        return;
215    }
216
217    oldName = [[_name retain] autorelease];
218
219    [_name release];
220    _name = [name retain];
221
222    [self notifyDelegateDidChangeNameFromOldValue: oldName];
223}
224
225- (float) opacity
226{
227    return _opacity;
228}
229
230- (void) setOpacity: (float) opacity
231{
232    [self setOpacity: opacity andRegisterUndo: YES];
233}
234
235- (void) setOpacityWithoutRegisteringUndo: (float) opacity
236{
237    [self setOpacity: opacity andRegisterUndo: NO];
238}
239
240- (void) increaseOpacity
241{
242    if (![self canIncreaseOpacity])
243    {
244        return;
245    }
246
247    [self setOpacity: _opacity + kOpacityStepSize];
248}
249
250- (bool) canIncreaseOpacity
251{
252    return (_opacity < 1.0f) ? YES : NO;
253}
254
255- (void) decreaseOpacity
256{
257    if (![self canDecreaseOpacity])
258    {
259        return;
260    }
261
262    [self setOpacity: _opacity - kOpacityStepSize];
263}
264
265- (bool) canDecreaseOpacity
266{
267    return (_opacity > 0.0f) ? YES : NO;
268}
269
270- (NSSize) size
271{
272    return _size;
273}
274
275- (PPDocumentLayer *) layerResizedToSize: (NSSize) newSize shouldScale: (bool) shouldScale
276{
277    NSBitmapImageRep *resizedBitmap;
278    NSData *resizedBitmapData;
279    PPDocumentLayer *resizedLayer;
280
281    newSize = PPGeometry_SizeClippedToIntegerValues(newSize);
282
283    resizedBitmap = [_bitmap ppBitmapResizedToSize: newSize shouldScale: shouldScale];
284
285    if (!resizedBitmap)
286        goto ERROR;
287
288    resizedBitmapData = [resizedBitmap TIFFRepresentation];
289
290    if (!resizedBitmapData)
291        goto ERROR;
292
293    resizedLayer = [[[PPDocumentLayer alloc] initWithSize: newSize
294                                                name: _name
295                                                tiffData: resizedBitmapData
296                                                opacity: _opacity
297                                                isEnabled: _isEnabled]
298                                        autorelease];
299
300    return resizedLayer;
301
302ERROR:
303    return nil;
304}
305
306- (PPDocumentLayer *) layerCroppedToBounds: (NSRect) croppingBounds
307{
308    NSData *croppedBitmapTIFFData;
309    PPDocumentLayer *croppedLayer;
310
311    croppingBounds = NSIntersectionRect(PPGeometry_PixelBoundsCoveredByRect(croppingBounds),
312                                        [_bitmap ppFrameInPixels]);
313
314    if (NSIsEmptyRect(croppingBounds))
315    {
316        goto ERROR;
317    }
318
319    croppedBitmapTIFFData = [_bitmap ppCompressedTIFFDataFromBounds: croppingBounds];
320
321    if (!croppedBitmapTIFFData)
322        goto ERROR;
323
324    croppedLayer = [[[PPDocumentLayer alloc] initWithSize: croppingBounds.size
325                                                name: _name
326                                                tiffData: croppedBitmapTIFFData
327                                                opacity: _opacity
328                                                isEnabled: _isEnabled]
329                                        autorelease];
330
331    return croppedLayer;
332
333ERROR:
334    return nil;
335}
336
337- (bool) enableLinearBlendingBitmap: (bool) enableLinearBlendingBitmap
338{
339    if (enableLinearBlendingBitmap)
340    {
341        if (!_linearBlendingBitmap)
342        {
343            _linearBlendingBitmap = [[_bitmap ppLinearRGB16BitmapFromImageBitmap] retain];
344        }
345
346        return (_linearBlendingBitmap) ? YES : NO;
347    }
348    else    // (!enableLinearBlendingBitmap)
349    {
350        if (_linearBlendingBitmap)
351        {
352            [_linearBlendingBitmap release];
353            _linearBlendingBitmap = nil;
354        }
355
356        return YES;
357    }
358}
359
360- (NSBitmapImageRep *) linearBlendingBitmap
361{
362    return _linearBlendingBitmap;
363}
364
365- (id) delegate
366{
367    return _delegate;
368}
369
370- (void) setDelegate: (id) delegate
371{
372    _delegate = delegate;
373}
374
375#pragma mark NSCoding protocol
376
377- (id) initWithCoder: (NSCoder *) aDecoder
378{
379    return [self initWithSize: [aDecoder decodeSizeForKey: kDrawingLayerCodingKey_Size]
380                    name: [aDecoder decodeObjectForKey: kDrawingLayerCodingKey_Name]
381                    tiffData: [aDecoder decodeObjectForKey: kDrawingLayerCodingKey_TIFFData]
382                    opacity: [aDecoder decodeFloatForKey: kDrawingLayerCodingKey_Opacity]
383                    isEnabled: [aDecoder decodeBoolForKey: kDrawingLayerCodingKey_IsEnabled]];
384}
385
386- (void) encodeWithCoder: (NSCoder *) coder
387{
388    [coder encodeSize: _size forKey: kDrawingLayerCodingKey_Size];
389    [coder encodeObject: _name forKey: kDrawingLayerCodingKey_Name];
390
391    [coder encodeObject: [_bitmap ppCompressedTIFFData]
392            forKey: kDrawingLayerCodingKey_TIFFData];
393
394    [coder encodeFloat: _opacity forKey: kDrawingLayerCodingKey_Opacity];
395    [coder encodeBool: _isEnabled forKey: kDrawingLayerCodingKey_IsEnabled];
396}
397
398#pragma mark NSCopying protocol
399
400- (id) copyWithZone: (NSZone *) zone
401{
402    PPDocumentLayer *layerCopy;
403    bool needToCopyLayerBitmapData = YES;
404
405    layerCopy = [[[self class] allocWithZone: zone] initWithSize: _size
406                                                    name: _name
407                                                    tiffData: nil
408                                                    opacity: _opacity
409                                                    isEnabled: _isEnabled];
410
411    if (!layerCopy)
412        goto ERROR;
413
414    layerCopy->_delegate = _delegate;
415
416    if (zone && (zone != NSDefaultMallocZone()))
417    {
418        if ([layerCopy->_name zone] != zone)
419        {
420            NSString *zonedName = [[_name copyWithZone: zone] autorelease];
421
422            if (zonedName)
423            {
424                [layerCopy->_name release];
425                layerCopy->_name = [zonedName retain];
426            }
427        }
428
429        if (([layerCopy->_bitmap zone] != zone)
430            || ([layerCopy->_image zone] != zone))
431        {
432            NSBitmapImageRep *zonedBitmap = [[_bitmap copyWithZone: zone] autorelease];
433            NSImage *zonedImage =
434                            [[[NSImage allocWithZone: zone] initWithSize: _size] autorelease];
435
436            if (zonedBitmap && zonedImage)
437            {
438                [zonedImage addRepresentation: zonedBitmap];
439
440                [layerCopy->_bitmap release];
441                layerCopy->_bitmap = [zonedBitmap retain];
442
443                [layerCopy->_image release];
444                layerCopy->_image = [zonedImage retain];
445
446                needToCopyLayerBitmapData = NO;
447            }
448        }
449    }
450
451    if (needToCopyLayerBitmapData)
452    {
453        [layerCopy->_bitmap ppCopyFromBitmap: _bitmap toPoint: NSZeroPoint];
454        [layerCopy handleUpdateToBitmapInRect: PPGeometry_OriginRectOfSize(_size)];
455    }
456
457    // Don't need to enable _linearBlendingBitmap in the copy - the linear bitmap will be
458    // enabled automatically if the copy's added to a PPDocument that's in linear blending mode,
459    // otherwise, the linear bitmap's currently unused in unattached layers.
460
461    return layerCopy;
462
463ERROR:
464    return nil;
465}
466
467#pragma mark Private methods
468
469- (void) setOpacity: (float) opacity andRegisterUndo: (bool) shouldRegisterUndo
470{
471    if (opacity > 1.0f)
472    {
473        opacity = 1.0f;
474    }
475    else if (opacity < 0.0f)
476    {
477        opacity = 0.0f;
478    }
479
480    if (!shouldRegisterUndo && (_opacity == opacity))
481    {
482        return;
483    }
484
485    _opacity = opacity;
486
487    [self notifyDelegateDidChangeOpacityAndShouldRegisterUndo: shouldRegisterUndo];
488
489    if (shouldRegisterUndo)
490    {
491        _lastOpacity = _opacity;
492    }
493}
494
495#pragma mark Delegate notifiers
496
497- (void) notifyDelegateDidChangeNameFromOldValue: (NSString *) oldName
498{
499    if ([_delegate respondsToSelector: @selector(layer:changedNameFromOldValue:)])
500    {
501        [_delegate layer: self changedNameFromOldValue: oldName];
502    }
503}
504
505- (void) notifyDelegateDidChangeEnabledFlagFromOldValue: (bool) oldEnabledFlag
506{
507    if ([_delegate respondsToSelector: @selector(layer:changedEnabledFlagFromOldValue:)])
508    {
509        [_delegate layer: self changedEnabledFlagFromOldValue: oldEnabledFlag];
510    }
511}
512
513- (void) notifyDelegateDidChangeOpacityAndShouldRegisterUndo: (bool) shouldRegisterUndo
514{
515    if ([_delegate respondsToSelector: @selector(layer:changedOpacityFromOldValue:
516                                                    shouldRegisterUndo:)])
517    {
518        [_delegate layer: self
519                    changedOpacityFromOldValue: _lastOpacity
520                    shouldRegisterUndo: shouldRegisterUndo];
521    }
522}
523
524@end
525