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