Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions lang/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -1412,8 +1412,13 @@
"No pending submissions for this game.": "No pending submissions for this game.",
"Submit another": "Submit another",
"Back to game": "Back to game",
"Expected Resolutions: {{resolutions}}": "Expected Resolutions: {{resolutions}}",
"Expected Resolutions: {{resolutions}} (or 2x/3x multiples)": "Expected Resolutions: {{resolutions}} (or 2x/3x multiples)",
"Supported resolutions: {{resolutions}}": "Supported resolutions: {{resolutions}}",
"Upscaled screenshots look sharper. Render at 2x or 3x in your emulator.": "Upscaled screenshots look sharper. Render at 2x or 3x in your emulator.",
"1x capture, render at 2x or 3x for a sharper screenshot": "1x capture, render at 2x or 3x for a sharper screenshot",
"Use your emulator's screenshot tool, ideally at 2x or 3x internal resolution.": "Use your emulator's screenshot tool, ideally at 2x or 3x internal resolution.",
"Use your emulator's screenshot tool, not a desktop capture.": "Use your emulator's screenshot tool, not a desktop capture.",
"Resolution doesn't match. See the preview above.": "Resolution doesn't match. See the preview above.",
"or 2x or 3x of any of these": "or 2x or 3x of any of these",
"Upload screenshot file": "Upload screenshot file",
"Valid resolution": "Valid resolution",
"Invalid resolution": "Invalid resolution",
Expand All @@ -1427,8 +1432,6 @@
"Click or drag to replace": "Click or drag to replace",
"Drop your screenshot here, or click to browse": "Drop your screenshot here, or click to browse",
"Primary screenshot has an incorrect resolution": "Primary screenshot has an incorrect resolution",
"This screenshot's dimensions ({{width}}x{{height}}) don't match the expected resolutions: {{resolutions}}.": "This screenshot's dimensions ({{width}}x{{height}}) don't match the expected resolutions: {{resolutions}}.",
"This screenshot's dimensions ({{width}}x{{height}}) don't match the expected resolutions: {{resolutions}} (or 2x/3x multiples).": "This screenshot's dimensions ({{width}}x{{height}}) don't match the expected resolutions: {{resolutions}} (or 2x/3x multiples).",
"Cancel submission": "Cancel submission",
"No emulators matched your search.": "No emulators matched your search.",
"This system only accepts PNG screenshots.": "This system only accepts PNG screenshots.",
Expand Down
48 changes: 48 additions & 0 deletions resources/js/common/utils/getIsNativeScreenshotResolution.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { getIsNativeScreenshotResolution } from './getIsNativeScreenshotResolution';

describe('Util: getIsNativeScreenshotResolution', () => {
const baseResolutions = [{ width: 256, height: 224 }];

it('given an exact 1x native match, returns true', () => {
// ASSERT
expect(getIsNativeScreenshotResolution(256, 224, baseResolutions)).toEqual(true);
});

it('given a 1px tolerance match against a native resolution, returns true', () => {
// ASSERT
expect(getIsNativeScreenshotResolution(257, 225, baseResolutions)).toEqual(true);
});

it('given a 2x or 3x scaled match, returns false', () => {
// ASSERT
expect(getIsNativeScreenshotResolution(512, 448, baseResolutions)).toEqual(false);
expect(getIsNativeScreenshotResolution(768, 672, baseResolutions)).toEqual(false);
});

it('given non-matching dimensions, returns false', () => {
// ASSERT
expect(getIsNativeScreenshotResolution(1920, 1080, baseResolutions)).toEqual(false);
});

it('given an empty native list with no analog TV output, returns false', () => {
// ASSERT
expect(getIsNativeScreenshotResolution(320, 240, [])).toEqual(false);
});

it('given an exact SMPTE 601 size, matches only when analog TV output is enabled', () => {
// ASSERT
expect(getIsNativeScreenshotResolution(720, 480, baseResolutions, true)).toEqual(true);
expect(getIsNativeScreenshotResolution(720, 480, baseResolutions, false)).toEqual(false);
});

it('given a 1px deviation from an SMPTE 601 size with analog output enabled, returns false', () => {
// ASSERT
expect(getIsNativeScreenshotResolution(720, 481, baseResolutions, true)).toEqual(false);
});

it('given an empty native list with analog output, only exact SMPTE sizes match', () => {
// ASSERT
expect(getIsNativeScreenshotResolution(720, 576, [], true)).toEqual(true);
expect(getIsNativeScreenshotResolution(1920, 1080, [], true)).toEqual(false);
});
});
36 changes: 36 additions & 0 deletions resources/js/common/utils/getIsNativeScreenshotResolution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { getIsSameScreenshotResolution } from './getIsSameScreenshotResolution';
import { SCREENSHOT_SMPTE_RESOLUTIONS } from './screenshotSmpteResolutions';

/**
* Returns true only when the dimensions are a 1x native capture, never a 2x/3x upscale.
*
* Diverges from getIsValidScreenshotResolution() in two important ways:
* - An empty native list returns false (rather than "any size is valid"). This prevents
* every upload on a system with no resolution metadata from being treated as native.
* - 2x/3x multiples never match here, even when the system supports upscaling.
*
* SMPTE 601 sizes count as 1x when hasAnalogTvOutput is true, since they're real hardware
* captures (not upscales), and use exact equality (no tolerance), matching the validator.
*/
export function getIsNativeScreenshotResolution(
width: number,
height: number,
nativeResolutions: Array<{ width: number; height: number }>,
hasAnalogTvOutput?: boolean,
): boolean {
for (const resolution of nativeResolutions) {
if (getIsSameScreenshotResolution(width, height, resolution.width, resolution.height)) {
return true;
}
}

if (hasAnalogTvOutput) {
for (const smpte of SCREENSHOT_SMPTE_RESOLUTIONS) {
if (width === smpte.width && height === smpte.height) {
return true;
}
}
}

return false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,12 @@ describe('Util: getIsValidScreenshotResolution', () => {
// ASSERT
expect(result).toEqual(true);
});

it('given a 1px deviation from an SMPTE 601 resolution with analog output enabled, returns false', () => {
// ACT
const result = getIsValidScreenshotResolution(720, 481, baseResolutions, true);

// ASSERT
expect(result).toEqual(false);
});
});
11 changes: 2 additions & 9 deletions resources/js/common/utils/getIsValidScreenshotResolution.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import { getIsSameScreenshotResolution } from './getIsSameScreenshotResolution';

const SMPTE_601_RESOLUTIONS = [
{ width: 704, height: 480 },
{ width: 720, height: 480 },
{ width: 720, height: 486 },
{ width: 704, height: 576 },
{ width: 720, height: 576 },
];
import { SCREENSHOT_SMPTE_RESOLUTIONS } from './screenshotSmpteResolutions';

export function getIsValidScreenshotResolution(
width: number,
Expand Down Expand Up @@ -35,7 +28,7 @@ export function getIsValidScreenshotResolution(

// SMPTE 601 analog capture resolutions are an exact-match check.
if (hasAnalogTvOutput) {
for (const smpte of SMPTE_601_RESOLUTIONS) {
for (const smpte of SCREENSHOT_SMPTE_RESOLUTIONS) {
if (width === smpte.width && height === smpte.height) {
return true;
}
Expand Down
11 changes: 11 additions & 0 deletions resources/js/common/utils/screenshotSmpteResolutions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* SMPTE 601 analog-capture resolutions, used by systems with `has_analog_tv_output`.
* Matched with no tolerance. These are well-defined standards.
*/
export const SCREENSHOT_SMPTE_RESOLUTIONS = [
{ width: 704, height: 480 },
{ width: 720, height: 480 },
{ width: 720, height: 486 },
{ width: 704, height: 576 },
{ width: 720, height: 576 },
] as const;
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,10 @@ describe('Component: GameScreenshotUploadDialog', () => {
render(<GameScreenshotUploadDialog isOpen={true} onOpenChange={vi.fn()} />, {
pageProps: {
game: createGame({
system: createSystem({ screenshotResolutions: [{ width: 320, height: 240 }] }),
system: createSystem({
screenshotResolutions: [{ width: 320, height: 240 }],
supportsUpscaledScreenshots: false,
}),
}),
screenshotUploadConsistency: {
existingResolutions: [{ width: 256, height: 224 }],
Expand Down
Loading
Loading