git.y1.nz

SameBoy

Accurate GB/GBC emulator
download: https://git.y1.nz/archives/sameboy.tar.gz
README | Files | Log | Refs | LICENSE

commit fc10a90dec70fe52e3c96e6ebe699a10412b0857
parent 3f954f1d0c32bd8d6fdfa00b9ff7e321c37951a6
Author: Lior Halphon <LIJI32@gmail.com>
Date:   Sat, 23 Oct 2021 13:26:44 +0300

Screenshots in the Cocoa frontend

Diffstat:
MCocoa/Document.m111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MCocoa/GBPreferencesWindow.h1+
MCocoa/GBPreferencesWindow.m25+++++++++++++++++++++++++
MCocoa/GBView.h1+
MCocoa/GBView.m6++++++
MCocoa/GBViewMetal.m21+++++++++++++++++++--
MCocoa/MainMenu.xib17+++++++++++++++++
MCocoa/Preferences.xib20++++++++++++++++----
8 files changed, 196 insertions(+), 6 deletions(-)

diff --git a/Cocoa/Document.m b/Cocoa/Document.m @@ -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 diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h @@ -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 diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m @@ -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 diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h @@ -27,4 +27,5 @@ typedef enum { - (uint32_t *)previousBuffer; - (void)screenSizeChanged; - (void)setRumble: (double)amp; +- (NSImage *)renderToImage; @end diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m @@ -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 diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m @@ -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 diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib @@ -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> diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib @@ -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"/>

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.