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