1/*
2    PPCanvasView_FillToolOverlay.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 "PPCanvasView.h"
26
27#import "NSBitmapImageRep_PPUtilities.h"
28#import "NSBezierPath_PPUtilities.h"
29#import "NSColor_PPUtilities.h"
30#import "PPGeometry.h"
31
32
33#define kUIColor_FillToolOverlayOutline                     \
34            [NSColor ppSRGBColorWithRed: 0.0f green: 0.0f blue: 1.0f alpha: 0.6f]
35
36
37static NSColor *gOverlayOutlineColor = nil;
38
39
40@interface PPCanvasView (FillToolOverlayPrivateMethods)
41
42- (NSPoint) fillColorPatternPhaseForCurrentViewGeometry;
43
44- (NSRect) visibleDrawingBoundsForFillToolPathWithBounds: (NSRect) pathBounds;
45
46@end
47
48@implementation PPCanvasView (FillToolOverlay)
49
50+ (void) initializeFillToolOverlay
51{
52    gOverlayOutlineColor = [kUIColor_FillToolOverlayOutline retain];
53}
54
55- (bool) initFillToolOverlayMembers
56{
57    _fillToolOverlayPath_Fill = [[NSBezierPath bezierPath] retain];
58    _fillToolOverlayPath_Outline = [[NSBezierPath bezierPath] retain];
59
60    if (!_fillToolOverlayPath_Fill || !_fillToolOverlayPath_Outline)
61    {
62        goto ERROR;
63    }
64
65    return YES;
66
67ERROR:
68    return NO;
69}
70
71- (void) deallocFillToolOverlayMembers
72{
73    [_fillToolOverlayPatternColor release];
74    _fillToolOverlayPatternColor = nil;
75
76    [_fillToolOverlayPath_Fill release];
77    _fillToolOverlayPath_Fill = nil;
78
79    [_fillToolOverlayPath_Outline release];
80    _fillToolOverlayPath_Outline = nil;
81}
82
83- (void) beginFillToolOverlayForOperationTarget: (PPLayerOperationTarget) operationTarget
84            fillColor: (NSColor *) fillColor;
85{
86    if (_fillToolOverlayPatternColor)
87    {
88        [self endFillToolOverlay];
89    }
90
91    if (!PPLayerOperationTarget_IsValid(operationTarget)
92        || !fillColor)
93    {
94        goto ERROR;
95    }
96
97    if (operationTarget == kPPLayerOperationTarget_DrawingLayerOnly)
98    {
99        return;
100    }
101
102    _fillToolOverlayPatternColor = [[NSColor ppFillOverlayPatternColorWithSize: _zoomFactor
103                                                fillColor: fillColor]
104                                        retain];
105
106    if (!_fillToolOverlayPatternColor)
107        goto ERROR;
108
109    _fillToolOverlayPatternPhase = [self fillColorPatternPhaseForCurrentViewGeometry];
110
111    return;
112
113ERROR:
114    return;
115}
116
117- (void) setFillToolOverlayToMask: (NSBitmapImageRep *) maskBitmap
118            maskBounds: (NSRect) maskBounds
119{
120    NSRect pathDrawingBounds;
121    NSAffineTransform *transform;
122    NSRect overlayBounds, visibleClippingBounds;
123
124    if (!_fillToolOverlayPatternColor)
125        return;
126
127    [_fillToolOverlayPath_Fill removeAllPoints];
128    [_fillToolOverlayPath_Outline removeAllPoints];
129
130    if (_shouldDisplayFillToolOverlay)
131    {
132        [self setNeedsDisplayInRect: _fillToolOverlayDisplayBounds];
133    }
134
135    if (![maskBitmap ppIsMaskBitmap])
136    {
137        goto ERROR;
138    }
139
140    pathDrawingBounds = [self visibleDrawingBoundsForFillToolPathWithBounds: maskBounds];
141
142    [_fillToolOverlayPath_Fill ppAppendFillPathForMaskBitmap: maskBitmap
143                                inBounds: pathDrawingBounds];
144
145    [_fillToolOverlayPath_Outline ppAppendOutlinePathForMaskBitmap: maskBitmap
146                                    inBounds: pathDrawingBounds];
147
148    transform = [NSAffineTransform transform];
149
150    if (!transform)
151        return;
152
153    [transform translateXBy: _canvasDrawingOffset.x + 0.5f
154                        yBy: _canvasDrawingOffset.y - 0.5f];
155
156    [transform scaleBy: _zoomFactor];
157
158    overlayBounds = NSZeroRect;
159
160    if (![_fillToolOverlayPath_Fill isEmpty])
161    {
162        [_fillToolOverlayPath_Fill transformUsingAffineTransform: transform];
163        [_fillToolOverlayPath_Outline transformUsingAffineTransform: transform];
164
165        overlayBounds = [_fillToolOverlayPath_Fill bounds];
166    }
167
168    _fillToolOverlayDisplayBounds = PPGeometry_PixelBoundsCoveredByRect(overlayBounds);
169
170    // allow the outline to extend one pixel beyond the right & bottom canvas edges
171    visibleClippingBounds = _offsetZoomedVisibleCanvasBounds;
172    visibleClippingBounds.size.width += 1.0f;
173    visibleClippingBounds.origin.y -= 1.0f;
174    visibleClippingBounds.size.height += 1.0f;
175
176    _fillToolOverlayDisplayBounds =
177                    NSIntersectionRect(_fillToolOverlayDisplayBounds, visibleClippingBounds);
178
179    _shouldDisplayFillToolOverlay = (NSIsEmptyRect(_fillToolOverlayDisplayBounds)) ? NO : YES;
180
181    if (_shouldDisplayFillToolOverlay)
182    {
183        [self setNeedsDisplayInRect: _fillToolOverlayDisplayBounds];
184    }
185
186    return;
187
188ERROR:
189    [self endFillToolOverlay];
190
191    return;
192}
193
194- (void) endFillToolOverlay
195{
196    [_fillToolOverlayPatternColor release];
197    _fillToolOverlayPatternColor = nil;
198
199    [_fillToolOverlayPath_Fill removeAllPoints];
200    [_fillToolOverlayPath_Outline removeAllPoints];
201
202    if (_shouldDisplayFillToolOverlay)
203    {
204        [self setNeedsDisplayInRect: _fillToolOverlayDisplayBounds];
205    }
206
207    _shouldDisplayFillToolOverlay = NO;
208    _fillToolOverlayDisplayBounds = NSZeroRect;
209}
210
211- (void) drawFillToolOverlay
212{
213    if (!_shouldDisplayFillToolOverlay)
214        return;
215
216    [_fillToolOverlayPatternColor set];
217    [[NSGraphicsContext currentContext] setPatternPhase: _fillToolOverlayPatternPhase];
218    [_fillToolOverlayPath_Fill fill];
219
220    [gOverlayOutlineColor set];
221    [_fillToolOverlayPath_Outline stroke];
222}
223
224#pragma mark Private methods
225
226- (NSPoint) fillColorPatternPhaseForCurrentViewGeometry
227{
228    NSScrollView *scrollView;
229    NSSize scrollViewFrameSize, contentViewFrameSize;
230    NSPoint visibleOrigin;
231
232    scrollView = [self enclosingScrollView];
233    scrollViewFrameSize = [scrollView frame].size;
234    contentViewFrameSize = [scrollView contentSize];
235    visibleOrigin = [[scrollView contentView] documentVisibleRect].origin;
236
237    return NSMakePoint(_canvasDrawingOffset.x - visibleOrigin.x,
238                        _canvasDrawingOffset.y
239                            + scrollViewFrameSize.height
240                            - contentViewFrameSize.height
241                            - visibleOrigin.y);
242}
243
244- (NSRect) visibleDrawingBoundsForFillToolPathWithBounds: (NSRect) pathBounds
245{
246    // outset drawing bounds from _visibleCanvasBounds by 2.0 in both directions - the extra
247    // (single-pixel) border around the visible canvas prevents false (cropped) path edges
248    // from appearing on the window
249
250    NSRect visibleCanvasDrawingBounds =
251                        NSIntersectionRect(NSInsetRect(_visibleCanvasBounds, -2.0f, -2.0f),
252                                            _canvasFrame);
253
254    return NSIntersectionRect(visibleCanvasDrawingBounds, pathBounds);
255}
256
257@end
258