Screenshots in the Cocoa frontend

This commit is contained in:
Lior Halphon 2021-10-23 13:26:44 +03:00
parent 3f954f1d0c
commit fc10a90dec
8 changed files with 196 additions and 6 deletions

View File

@ -2201,4 +2201,115 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
{
return &gb;
}
- (NSImage *)takeScreenshot
{
NSImage *ret = nil;
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBFilterScreenshots"]) {
ret = [_view renderToImage];
}
if (!ret) {
ret = [Document imageFromData:[NSData dataWithBytesNoCopy:_view.currentBuffer
length:GB_get_screen_width(&gb) * GB_get_screen_height(&gb) * 4
freeWhenDone:false]
width:GB_get_screen_width(&gb)
height:GB_get_screen_height(&gb)
scale:1.0];
}
[ret lockFocus];
NSBitmapImageRep *bitmapRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect(0, 0,
ret.size.width, ret.size.height)];
[ret unlockFocus];
ret = [[NSImage alloc] initWithSize:ret.size];
[ret addRepresentation:bitmapRep];
return ret;
}
- (NSString *)screenshotFilename
{
NSDate *date = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateStyle = NSDateFormatterLongStyle;
dateFormatter.timeStyle = NSDateFormatterMediumStyle;
return [[NSString stringWithFormat:@"%@ %@.png",
self.fileURL.lastPathComponent.stringByDeletingPathExtension,
[dateFormatter stringFromDate:date]] stringByReplacingOccurrencesOfString:@":" withString:@"."]; // Gotta love Mac OS Classic
}
- (IBAction)saveScreenshot:(id)sender
{
NSString *folder = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBScreenshotFolder"];
BOOL isDirectory = false;
if (folder) {
[[NSFileManager defaultManager] fileExistsAtPath:folder isDirectory:&isDirectory];
}
if (!folder) {
bool shouldResume = running;
[self stop];
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
openPanel.canChooseFiles = false;
openPanel.canChooseDirectories = true;
openPanel.message = @"Choose a folder for screenshots";
[openPanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) {
if (result == NSModalResponseOK) {
[[NSUserDefaults standardUserDefaults] setObject:openPanel.URL.path
forKey:@"GBScreenshotFolder"];
[self saveScreenshot:sender];
}
if (shouldResume) {
[self start];
}
}];
return;
}
NSImage *image = [self takeScreenshot];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateStyle = NSDateFormatterLongStyle;
dateFormatter.timeStyle = NSDateFormatterMediumStyle;
NSString *filename = [self screenshotFilename];
filename = [folder stringByAppendingPathComponent:filename];
unsigned i = 2;
while ([[NSFileManager defaultManager] fileExistsAtPath:filename]) {
filename = [[filename stringByDeletingPathExtension] stringByAppendingFormat:@" %d.png", i++];
}
NSBitmapImageRep *imageRep = (NSBitmapImageRep *)image.representations.firstObject;
NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
[data writeToFile:filename atomically:NO];
[self.osdView displayText:@"Screenshot saved"];
}
- (IBAction)saveScreenshotAs:(id)sender
{
bool shouldResume = running;
[self stop];
NSImage *image = [self takeScreenshot];
NSSavePanel *savePanel = [NSSavePanel savePanel];
[savePanel setNameFieldStringValue:[self screenshotFilename]];
[savePanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) {
if (result == NSModalResponseOK) {
[savePanel orderOut:self];
NSBitmapImageRep *imageRep = (NSBitmapImageRep *)image.representations.firstObject;
NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
[data writeToURL:savePanel.URL atomically:NO];
[[NSUserDefaults standardUserDefaults] setObject:savePanel.URL.path.stringByDeletingLastPathComponent
forKey:@"GBScreenshotFolder"];
}
if (shouldResume) {
[self start];
}
}];
[self.osdView displayText:@"Screenshot saved"];
}
- (IBAction)copyScreenshot:(id)sender
{
NSImage *image = [self takeScreenshot];
[[NSPasteboard generalPasteboard] clearContents];
[[NSPasteboard generalPasteboard] writeObjects:@[image]];
[self.osdView displayText:@"Screenshot copied"];
}
@end

View File

@ -28,4 +28,5 @@
@property (nonatomic, weak) IBOutlet NSButton *autoUpdatesCheckbox;
@property (weak) IBOutlet NSSlider *volumeSlider;
@property (weak) IBOutlet NSButton *OSDCheckbox;
@property (weak) IBOutlet NSButton *screenshotFilterCheckbox;
@end

View File

@ -2,6 +2,7 @@
#import "NSString+StringForKey.h"
#import "GBButtons.h"
#import "BigSurToolbar.h"
#import "GBViewMetal.h"
#import <Carbon/Carbon.h>
@implementation GBPreferencesWindow
@ -32,6 +33,7 @@
NSSlider *_volumeSlider;
NSButton *_autoUpdatesCheckbox;
NSButton *_OSDCheckbox;
NSButton *_screenshotFilterCheckbox;
}
+ (NSArray *)filterList
@ -766,4 +768,27 @@
forKey:@"GBOSDEnabled"];
}
- (IBAction)changeFilterScreenshots:(id)sender
{
[[NSUserDefaults standardUserDefaults] setBool:[(NSButton *)sender state] == NSOnState
forKey:@"GBFilterScreenshots"];
}
- (NSButton *)screenshotFilterCheckbox
{
return _screenshotFilterCheckbox;
}
- (void)setScreenshotFilterCheckbox:(NSButton *)screenshotFilterCheckbox
{
_screenshotFilterCheckbox = screenshotFilterCheckbox;
if (![GBViewMetal isSupported]) {
[_screenshotFilterCheckbox setEnabled:false];
}
else {
[_screenshotFilterCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBFilterScreenshots"]];
}
}
@end

View File

@ -27,4 +27,5 @@ typedef enum {
- (uint32_t *)previousBuffer;
- (void)screenSizeChanged;
- (void)setRumble: (double)amp;
- (NSImage *)renderToImage;
@end

View File

@ -677,4 +677,10 @@ static const uint8_t workboy_vk_to_key[] = {
return false;
}
- (NSImage *)renderToImage;
{
/* Not going to support this on OpenGL, OpenGL is too much of a terrible API for me
to bother figuring out how the hell something so trivial can be done. */
return nil;
}
@end

View File

@ -1,3 +1,4 @@
#import <CoreImage/CoreImage.h>
#import "GBViewMetal.h"
#pragma clang diagnostic ignored "-Wpartial-availability"
@ -51,8 +52,9 @@ static const vector_float2 rect[] =
MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())];
view.delegate = self;
self.internalView = view;
view.paused = YES;
view.enableSetNeedsDisplay = YES;
view.paused = true;
view.enableSetNeedsDisplay = true;
view.framebufferOnly = false;
vertices = [device newBufferWithBytes:rect
length:sizeof(rect)
@ -212,4 +214,19 @@ static const vector_float2 rect[] =
});
}
- (NSImage *)renderToImage
{
CIImage *ciImage = [CIImage imageWithMTLTexture:[[(MTKView *)self.internalView currentDrawable] texture]
options:@{
kCIImageColorSpace: (__bridge_transfer id)CGColorSpaceCreateDeviceRGB()
}];
ciImage = [ciImage imageByApplyingTransform:CGAffineTransformTranslate(CGAffineTransformMakeScale(1, -1),
0, ciImage.extent.size.height)];
CIContext *context = [CIContext context];
CGImageRef cgImage = [context createCGImage:ciImage fromRect:ciImage.extent];
NSImage *ret = [[NSImage alloc] initWithCGImage:cgImage size:self.internalView.bounds.size];
CGImageRelease(cgImage);
return ret;
}
@end

View File

@ -316,6 +316,23 @@
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="5GS-tt-E0a"/>
<menuItem title="Save Screenshot" keyEquivalent="s" id="0J3-yf-iXs">
<connections>
<action selector="saveScreenshot:" target="-1" id="gJd-ml-J8p"/>
</connections>
</menuItem>
<menuItem title="Save Screenshot As…" alternate="YES" keyEquivalent="s" id="98X-Fp-Uny">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="saveScreenshotAs:" target="-1" id="Cxc-Gx-ql1"/>
</connections>
</menuItem>
<menuItem title="Copy Screenshot" keyEquivalent="S" id="vbX-pB-QC8">
<connections>
<action selector="copyScreenshot:" target="-1" id="XJC-EB-HNl"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="zk7-gf-LXN"/>
<menuItem title="Game Boy" tag="1" id="g7C-LA-VAr">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>

View File

@ -90,6 +90,7 @@
<outlet property="rewindPopupButton" destination="7fg-Ww-JjR" id="Ka2-TP-B1x"/>
<outlet property="rtcPopupButton" destination="tFf-H1-XUL" id="zxb-4h-aqg"/>
<outlet property="rumbleModePopupButton" destination="Ogs-xG-b4b" id="vuw-VN-MTp"/>
<outlet property="screenshotFilterCheckbox" destination="spQ-Md-OFi" id="f9y-Ek-XQV"/>
<outlet property="sgbPopupButton" destination="dza-T7-RkX" id="B0o-Nb-pIH"/>
<outlet property="skipButton" destination="d2I-jU-sLb" id="udX-8K-0sK"/>
<outlet property="temperatureSlider" destination="NuA-mL-AJZ" id="w11-n7-Bmj"/>
@ -98,11 +99,11 @@
<point key="canvasLocation" x="183" y="354"/>
</window>
<customView id="sRK-wO-K6R">
<rect key="frame" x="0.0" y="0.0" width="320" height="399"/>
<rect key="frame" x="0.0" y="0.0" width="320" height="421"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T91-rh-rRp">
<rect key="frame" x="18" y="362" width="284" height="17"/>
<rect key="frame" x="18" y="384" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Graphics filter:" id="pXg-WY-8Q5">
<font key="font" metaFont="system"/>
@ -111,7 +112,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6pP-kK-EEC">
<rect key="frame" x="30" y="329" width="262" height="26"/>
<rect key="frame" x="30" y="351" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Nearest neighbor (Pixelated)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="neN-eo-LA7" id="I1w-05-lGl">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -147,6 +148,17 @@
<action selector="graphicFilterChanged:" target="QvC-M9-y7g" id="n87-t4-fbV"/>
</connections>
</popUpButton>
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="spQ-Md-OFi">
<rect key="frame" x="32" y="330" width="259" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Apply filter to screenshots" bezelStyle="regularSquare" imagePosition="left" inset="2" id="JbP-bE-w8A">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="changeFilterScreenshots:" target="QvC-M9-y7g" id="t82-FI-eSe"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wc3-2K-6CD">
<rect key="frame" x="18" y="307" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
@ -307,7 +319,7 @@
</connections>
</button>
</subviews>
<point key="canvasLocation" x="-176" y="679.5"/>
<point key="canvasLocation" x="-176" y="690.5"/>
</customView>
<customView id="ymk-46-SX7">
<rect key="frame" x="0.0" y="0.0" width="320" height="375"/>