1/* 2 PPGNUstepGlue_ImageRecacheSpeedups.m 3 4 Copyright 2014-2018 Josh Freeman 5 http://www.twilightedge.com 6 7 This file is part of PikoPixel for 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#ifdef GNUSTEP 26 27#import <Cocoa/Cocoa.h> 28#import "NSObject_PPUtilities.h" 29#import "PPAppBootUtilities.h" 30#import "PPDocument.h" 31#import "PPCanvasView.h" 32#import "PPDocumentLayer.h" 33#import "PPDocument_Notifications.h" 34#import "NSBitmapImageRep_PPUtilities.h" 35#import "PPGeometry.h" 36 37// An NSImage with no cached native-format representations (such as an image that hasn't been 38// drawn yet or an image that's been sent an -[NSImage recache] message) needs a native-format 39// representation to be generated (internally) before it can be drawn to a graphics context. 40// The native-format representation is always generated for the entire image (and it's 41// relatively slow to generate on GNUstep, compared to OS X), even when only a small part of 42// the image is being drawn to the graphics context. It's also inefficient to delete & 43// regenerate the entire native representation when only a small part of the image's source 44// bitmap representation has changed (such as when dragging a drawing tool). 45// Workaround/speedup intercepts recache messages to prevent the cached native representation 46// from being deleted & regenerated, and instead updates the cached native representation 47// directly by drawing the updated area of the source bitmap representation onto it. (NSImage's 48// lockFocus sets the image's native representation to be the target graphics context). 49 50 51// For now, disable patch of -[PPDocument recacheDissolvedDrawingLayerThumbnailImageInBounds:], 52// because _dissolvedDrawingLayerThumbnailImage is currently only drawn in one place: on the 53// navigator popup when the canvas' view mode is set to draw-layer-only. 54// The recache speedup patch is most useful on images that are redrawn often, but the 55// additional slowdown (updating the native representation each time the image is recached) 56// probably isn't worth it for a rarely-drawn image like _dissolvedDrawingLayerThumbnailImage. 57#define SHOULD_PATCH_PPDOCUMENT_RECACHEDISSOLVEDDRAWINGLAYERTHUMBNAILIMAGEINBOUNDS (false) 58 59 60@implementation NSObject (PPGNUstepGlue_ImageRecacheSpeedups) 61 62+ (void) ppGSGlue_ImageRecacheSpeedups_InstallPatches 63{ 64 macroSwizzleInstanceMethod(PPDocument, recacheMergedVisibleLayersThumbnailImageInBounds:, 65 ppGSPatch_RecacheMergedVisibleLayersThumbnailImageInBounds:); 66 67#if SHOULD_PATCH_PPDOCUMENT_RECACHEDISSOLVEDDRAWINGLAYERTHUMBNAILIMAGEINBOUNDS 68 69 macroSwizzleInstanceMethod(PPDocument, recacheDissolvedDrawingLayerThumbnailImageInBounds:, 70 ppGSPatch_RecacheDissolvedDrawingLayerThumbnailImageInBounds:); 71 72#endif // SHOULD_PATCH_PPDOCUMENT_RECACHEDISSOLVEDDRAWINGLAYERTHUMBNAILIMAGEINBOUNDS 73 74 macroSwizzleInstanceMethod(PPDocument, handleUpdateToInteractiveMoveTargetBitmapInBounds:, 75 ppGSPatch_HandleUpdateToInteractiveMoveTargetBitmapInBounds:); 76 77 78 macroSwizzleInstanceMethod(PPCanvasView, recacheZoomedVisibleCanvasImageInBounds:, 79 ppGSPatch_RecacheZoomedVisibleCanvasImageInBounds:); 80 81 82 macroSwizzleInstanceMethod(PPDocumentLayer, handleUpdateToBitmapInRect:, 83 ppGSPatch_HandleUpdateToBitmapInRect:); 84} 85 86+ (void) load 87{ 88 macroPerformNSObjectSelectorAfterAppLoads(ppGSGlue_ImageRecacheSpeedups_InstallPatches); 89} 90 91@end 92 93@implementation PPDocument (PPGNUstepGlue_ImageRecacheSpeedups) 94 95- (void) ppGSPatch_RecacheMergedVisibleLayersThumbnailImageInBounds: (NSRect) bounds 96{ 97 if (NSIsEmptyRect(bounds)) 98 { 99 return; 100 } 101 102 [_mergedVisibleLayersThumbnailImage lockFocus]; 103 104 [[_mergedVisibleLayersBitmap ppShallowDuplicateFromBounds: bounds] drawInRect: bounds]; 105 106 [_mergedVisibleLayersThumbnailImage unlockFocus]; 107} 108 109#if SHOULD_PATCH_PPDOCUMENT_RECACHEDISSOLVEDDRAWINGLAYERTHUMBNAILIMAGEINBOUNDS 110 111- (void) ppGSPatch_RecacheDissolvedDrawingLayerThumbnailImageInBounds: (NSRect) bounds 112{ 113 if (NSIsEmptyRect(bounds)) 114 { 115 return; 116 } 117 118 [_dissolvedDrawingLayerThumbnailImage lockFocus]; 119 120 [[_dissolvedDrawingLayerBitmap ppShallowDuplicateFromBounds: bounds] drawInRect: bounds]; 121 122 [_dissolvedDrawingLayerThumbnailImage unlockFocus]; 123} 124 125#endif // SHOULD_PATCH_PPDOCUMENT_RECACHEDISSOLVEDDRAWINGLAYERTHUMBNAILIMAGEINBOUNDS 126 127- (void) ppGSPatch_HandleUpdateToInteractiveMoveTargetBitmapInBounds: (NSRect) bounds 128{ 129 if (NSIsEmptyRect(bounds)) 130 { 131 return; 132 } 133 134 if (_interactiveMoveDisplayMode == kPPLayerDisplayMode_DrawingLayerOnly) 135 { 136 [_drawingLayer handleUpdateToBitmapInRect: bounds]; 137 138 [self handleUpdateToLayerAtIndex: _indexOfDrawingLayer inRect: bounds]; 139 } 140 else 141 { 142 // patch replaces original method's call to [_mergedVisibleLayersThumbnailImage recache] 143 144 [_mergedVisibleLayersThumbnailImage lockFocus]; 145 146 [[_mergedVisibleLayersBitmap ppShallowDuplicateFromBounds: bounds] drawInRect: bounds]; 147 148 [_mergedVisibleLayersThumbnailImage unlockFocus]; 149 150 [self postNotification_UpdatedMergedVisibleAreaInRect: bounds]; 151 } 152} 153 154@end 155 156@implementation PPCanvasView (PPGNUstepGlue_ImageRecacheSpeedups) 157 158- (void) ppGSPatch_RecacheZoomedVisibleCanvasImageInBounds: (NSRect) bounds 159{ 160 if (NSIsEmptyRect(bounds)) 161 { 162 return; 163 } 164 165 [_zoomedVisibleCanvasImage lockFocus]; 166 167 [[_zoomedVisibleCanvasBitmap ppShallowDuplicateFromBounds: bounds] drawInRect: bounds]; 168 169 [_zoomedVisibleCanvasImage unlockFocus]; 170} 171 172@end 173 174@implementation PPDocumentLayer (PPGNUstepGlue_ImageRecacheSpeedups) 175 176- (void) ppGSPatch_HandleUpdateToBitmapInRect: (NSRect) updateRect 177{ 178 updateRect = NSIntersectionRect(PPGeometry_OriginRectOfSize(_size), updateRect); 179 180 if (NSIsEmptyRect(updateRect)) 181 { 182 return; 183 } 184 185 [_image lockFocus]; 186 187 [[_bitmap ppShallowDuplicateFromBounds: updateRect] drawInRect: updateRect]; 188 189 [_image unlockFocus]; 190 191 if (_linearBlendingBitmap) 192 { 193 [_linearBlendingBitmap ppLinearCopyFromImageBitmap: _bitmap inBounds: updateRect]; 194 } 195} 196 197@end 198 199#endif // GNUSTEP 200 201