mirror of https://github.com/bsnes-emu/bsnes.git
Screenshots in the Cocoa frontend
This commit is contained in:
parent
3f954f1d0c
commit
fc10a90dec
111
Cocoa/Document.m
111
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -27,4 +27,5 @@ typedef enum {
|
|||
- (uint32_t *)previousBuffer;
|
||||
- (void)screenSizeChanged;
|
||||
- (void)setRumble: (double)amp;
|
||||
- (NSImage *)renderToImage;
|
||||
@end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
Loading…
Reference in New Issue