fix(screenshots): use Pro captures and native 14-inch Mac frames

This commit is contained in:
Kaede Akino committed 2026-10-07 02:14:35 +08:00
1 parent 875968ea40
commit 6c88844ffb
5 files changed
+86 -77

No files matched your search

+25 -24
View File
@@ -77,6 +77,8 @@ bundle exec fastlane ios screenshots plan:true
`output:/absolute/path` stages a review capture outside the source asset tree.
The default remains `com.akinokaede.termind/assets/captures/<locale>/<device>/<feature>.png`.
Capture and frame directories use `iphone-6.3`, `ipad-15`, and `mac-14`; command
selectors remain `iphone`, `ipad`, and `mac`. Duo selectors keep their existing directory names.
Logs and build products live under `.build/fastlane-screenshots/<platform>/`;
each run has its own logs. Existing captures are backed up with `SHA256SUMS` under
`.build/screenshot-backups/<run>/` before replacement. A failed test, missing
@@ -95,7 +97,7 @@ the downstream workflow described below.
produces Command Generator, and `agent` produces Assistant. Metrics detail is a
development demo only; do not retain its captures as marketing assets.
The mobile lane requires iOS/iPadOS 27. iPhone 18 Pro Max and iPad Pro use
The mobile lane requires iOS/iPadOS 27. iPhone 18 Pro and iPad Pro use
**iOS/iPadOS 27.0**; Duo uses **iOS 27.1**. The lane selects 27.1 for Duo-only
batches and 27.0 otherwise. The installed 27.1 runtime supports only Duo, so
capture it separately from iPhone and iPad.
@@ -103,7 +105,7 @@ It checks both the simulator model identifier and the raw pixel dimensions:
| Selector | Capture device | Native capture size |
| --- | --- | --- |
| `iphone` | iPhone 18 Pro Max (6.9-inch), portrait | 1320×2868 |
| `iphone` | iPhone 18 Pro (6.3-inch), portrait | 1206×2622 |
| `iphone-duo-outer` | iPhone Duo, closed, portrait | 1398×2034 |
| `iphone-duo-inner` | iPhone Duo, open, landscape | 2853×2007 |
| `ipad` | iPad Pro 13-inch (M5), landscape | 2752×2064 |
@@ -111,13 +113,13 @@ It checks both the simulator model identifier and the raw pixel dimensions:
Create the exact simulators once if they are missing:
```sh
xcrun simctl create "iPhone 18 Pro Max" com.apple.CoreSimulator.SimDeviceType.iPhone-18-Pro-Max com.apple.CoreSimulator.SimRuntime.iOS-27-0
xcrun simctl create "iPhone 18 Pro" com.apple.CoreSimulator.SimDeviceType.iPhone-18-Pro com.apple.CoreSimulator.SimRuntime.iOS-27-0
xcrun simctl create "iPhone Duo" com.apple.CoreSimulator.SimDeviceType.iPhone-Duo com.apple.CoreSimulator.SimRuntime.iOS-27-1
xcrun simctl create "iPad Pro 13-inch (M5)" com.apple.CoreSimulator.SimDeviceType.iPad-Pro-13-inch-M5-12GB com.apple.CoreSimulator.SimRuntime.iOS-27-0
```
The lane does not resize iPhone captures or substitute another device. Existing
`com.akinokaede.termind/assets/app_store/*/iphone-6.9` artwork uses a separate 1320×2868 composition canvas;
`com.akinokaede.termind/assets/app_store/*/iphone-6.3` artwork uses a separate 1206×2622 composition canvas;
that folder describes the final artwork, not the screenshot device. Recapturing
does not regenerate those downstream assets automatically.
@@ -172,9 +174,9 @@ outside can leave a hovered row highlighted in the captured image.
In Figma, keep Mac artwork 02–06 as native windows in all three languages.
Mac and iPad artwork 01 retain the three-device composition. iPhone artwork 01
uses two devices with aligned bottom edges: iPhone Assistant fully visible in
the right foreground (width 820), with Mac Workspace enlarged behind it (width
1800). The Mac layer deliberately extends left of the clipping Product imagery
frame; only part of the laptop is shown. No iPad appears in this composition.
the right foreground, with Mac Workspace enlarged behind it. Keep their
relative sizes and compact overlap when adjusting the 1206×2622 canvas. The Mac
layer deliberately extends left of the clipping Product imagery frame; only part of the laptop is shown. No iPad appears in this composition.
Keep the existing export filenames. Replace screenshot layers while
preserving their geometry, text, shared shadows, and stacking order, then export
the affected boards as opaque RGB JPGs with an sRGB ICC profile. Refresh the mobile
@@ -198,18 +200,18 @@ selected jobs without changing files. Defaults include only website-used scenes:
iPhone Assistant/SSH/Vault, iPad Metrics/SFTP/Vault, and Mac Workspace, in all three
locales. Use `scenes:` explicitly when another composition is needed.
The lane stages source copies and publishes
`<locale>/<device>/<feature>.png` only after every selected output succeeds. Old Pro captures
must be recaptured on iPhone 18 Pro Max before framing; they are rejected by size.
`<locale>/<device>/<feature>.png` only after every selected output succeeds. Old Pro Max captures
must be recaptured on iPhone 18 Pro before framing; they are rejected by size.
- iPhone uses Apple's official iPhone 18 Pro Max Silver portrait frame at native
- iPhone uses Apple's official iPhone 18 Pro Silver portrait frame at native
screenshot size. Import it locally or pass `iphone_frame:/path/to/portrait.png`.
- iPad uses Apple's official iPad Pro (M5) 13-inch Space Black PNG as a custom
frameit device. Its opening matches the native capture exactly, with no resize.
Import the PNG once as described in the local frame templates section below, or pass
`ipad_frame:/path/to/portrait.png`. The licensed source artwork stays local.
- Mac Workspace fills the MacBook frame with the full-screen app capture.
Capture aspect ratio is preserved while trimming the top of the black camera
strip to match the aperture. No wallpaper or Dock is composited. Other Mac scenes stay as native
The 14-inch aperture matches the capture at native scale. No wallpaper or Dock
is composited. Other Mac scenes stay as native
window captures for final artwork.
Frame dimensions now come from frameit. Astro imports image metadata directly,
@@ -220,12 +222,12 @@ the upstream device artwork; follow its usage terms.
## Local frame templates
### Official iPhone 18 Pro Max frame
### Official iPhone 18 Pro frame
Download the [iPhone 18 bezels](https://devimages-cdn.apple.com/design/resources/download/Bezel-iPhone-18.dmg)
from Apple Design Resources. Copy `PNG/iPhone 18 Pro Max/iPhone 18 Pro Max - Silver - Portrait.png`
to `.build/frameit-assets/iphone-18-pro-max.png`, or pass `iphone_frame:/path/to/frame.png`.
The template is 1470×3000 with a native 1320×2868 opening at (75, 66).
from Apple Design Resources. Copy `PNG/iPhone 18 Pro/iPhone 18 Pro - Silver - Portrait.png`
to `.build/frameit-assets/iphone-18-pro.png`, or pass `iphone_frame:/path/to/frame.png`.
The template is 1350×2760 with a native 1206×2622 opening at (72, 69).
The lane registers the template as a custom frameit device. Keep the licensed source local.
### Official iPhone Duo frames
@@ -272,23 +274,22 @@ M5 frame, so the lane registers this device at runtime.
### MacBook Pro narrow frame
Mac Overview compositions use Apple's MacBook Pro M5 16-inch Space Black PNG,
prepared locally as `.build/frameit-assets/macbook_pro_template.png`. Supply an
Mac Overview compositions use Apple's MacBook Pro M5 14-inch Space Black PNG,
prepared locally as `.build/frameit-assets/macbook-pro-14-template.png`. Supply an
alternate location with `mac_frame:/absolute/path/to/frame.png`. Keep the source
artwork outside version control and retain its original usage terms.
Download the MacBook Pro M5 bezel from Apple's Design Resources and accept its
license locally. Remove only the 220-pixel transparent top margin from the
official 4260×2840 PNG:
official 3860×2540 PNG:
```sh
magick "/Volumes/Bezel-MacBook-Pro-M5/PNG/MacBook Pro M5 16-inch Space Black.png" \
-crop 4260x2620+0+220 +repage .build/frameit-assets/macbook_pro_template.png
magick "/Volumes/Bezel-MacBook-Pro-M5/PNG/MacBook Pro M5 14-inch Space Black.png" \
-crop 3860x2320+0+220 +repage .build/frameit-assets/macbook-pro-14-template.png
```
The prepared transparent template is 4260×2620, with a 3456×2234 screen opening
at (402, 83). The native 3024×1964 capture scales proportionally to 3456×2245;
11 pixels are trimmed from its black full-screen camera strip to fill the opening.
The prepared transparent template is 3860×2320, with a 3024×1964 screen opening
at (418, 68). The native Workspace capture fits without resizing or trimming.
No desktop or personal system UI is added. Do not reuse a template extracted from
a composed screenshot: it can retain wallpaper or mismatched rounded corners.
A missing template fails before publishing outputs.
+41 -36
View File
@@ -30,8 +30,8 @@ class ScreenshotsTests < Minitest::Test
source = File.join(@root, "capture.png")
File.binwrite(source, "review capture")
capture = runner(:ios, output: "review")
capture.promote([[source, "en/ipad/vault.png"]])
assert_equal "review capture", File.binread(File.join(@root, "review/en/ipad/vault.png"))
capture.promote([[source, "en/ipad-15/vault.png"]])
assert_equal "review capture", File.binread(File.join(@root, "review/en/ipad-15/vault.png"))
refute Dir.exist?(File.join(@root, "com.akinokaede.termind"))
end
@@ -56,18 +56,18 @@ class ScreenshotsTests < Minitest::Test
runner(:ios, { devices: "iphone-duo-inner", runtime: "com.apple.CoreSimulator.SimRuntime.iOS-27-0" }).plan[:runtime]
end
def test_a_renamed_pro_cannot_be_used_as_the_pro_max_capture_device
def test_a_renamed_pro_max_cannot_be_used_as_the_pro_capture_device
capture = runner(:ios, devices: "iphone")
capture.define_singleton_method(:command) do |*args|
JSON.generate("devices" => { "com.apple.CoreSimulator.SimRuntime.iOS-27-0" => [
{ "name" => "iPhone 18 Pro Max", "udid" => "wrong-model",
"deviceTypeIdentifier" => "com.apple.CoreSimulator.SimDeviceType.iPhone-18-Pro" }
{ "name" => "iPhone 18 Pro", "udid" => "wrong-model",
"deviceTypeIdentifier" => "com.apple.CoreSimulator.SimDeviceType.iPhone-18-Pro-Max" }
] })
end
assert_raises(RuntimeError) { capture.find_devices }
capture.define_singleton_method(:dimensions) { |_| [1206, 2622] }
capture.define_singleton_method(:dimensions) { |_| [1320, 2868] }
assert_raises(RuntimeError) do
capture.validate_dimensions("pro.png", Screenshots::DEVICES.fetch("iphone").fetch(:dimensions))
capture.validate_dimensions("pro-max.png", Screenshots::DEVICES.fetch("iphone").fetch(:dimensions))
end
end
@@ -116,17 +116,17 @@ class ScreenshotsTests < Minitest::Test
def test_successful_capture_backs_up_original_bytes_and_manifest
capture = runner
original = File.join(@root, "com.akinokaede.termind/assets/captures/en/ipad/vault.png")
original = File.join(@root, "com.akinokaede.termind/assets/captures/en/ipad-15/vault.png")
FileUtils.mkdir_p(File.dirname(original))
File.binwrite(original, "old image bytes")
source = File.join(@root, "new.png")
File.binwrite(source, "new image bytes")
capture.promote([[source, "en/ipad/vault.png"]])
capture.promote([[source, "en/ipad-15/vault.png"]])
assert_equal "new image bytes", File.binread(original)
backup = Dir.glob(File.join(@root, ".build/screenshot-backups/*/en/ipad/vault.png")).fetch(0)
backup = Dir.glob(File.join(@root, ".build/screenshot-backups/*/en/ipad-15/vault.png")).fetch(0)
assert_equal "old image bytes", File.binread(backup)
manifest = File.read(File.join(File.dirname(File.dirname(File.dirname(backup))), "SHA256SUMS"))
assert_equal "#{Digest::SHA256.hexdigest('old image bytes')} en/ipad/vault.png\n", manifest
assert_equal "#{Digest::SHA256.hexdigest('old image bytes')} en/ipad-15/vault.png\n", manifest
end
def test_mac_captures_publish_localized_underscore_filenames
@@ -146,7 +146,7 @@ class ScreenshotsTests < Minitest::Test
end
capture.define_singleton_method(:dimensions) { |_| [2360, 1480] }
capture.capture_mac(scene, "zh-Hant", "platform=macOS")
destination = File.join(@root, "com.akinokaede.termind/assets/captures/zh-hant/mac/#{filename}.png")
destination = File.join(@root, "com.akinokaede.termind/assets/captures/zh-hant/mac-14/#{filename}.png")
assert_equal "captured #{scene}", File.binread(destination)
end
end
@@ -179,7 +179,7 @@ class ScreenshotsTests < Minitest::Test
end
def test_mobile_capture_rediscovers_container_after_host_reinstallation
%w[iphone iphone-duo-outer iphone-duo-inner].each do |device|
%w[iphone ipad iphone-duo-outer iphone-duo-inner].each do |device|
assert_mobile_capture_rediscovers_container(device)
end
end
@@ -223,7 +223,8 @@ class ScreenshotsTests < Minitest::Test
Open3.stub(:capture2e, responses) do
capture.capture_mobile(device, { "udid" => "fixture", "state" => "Booted" }, "zh-Hans")
end
destination = File.join(@root, "com.akinokaede.termind/assets/captures/zh-hans/#{device}/vault.png")
folder = { "iphone" => "iphone-6.3", "ipad" => "ipad-15" }.fetch(device, device)
destination = File.join(@root, "com.akinokaede.termind/assets/captures/zh-hans/#{folder}/vault.png")
assert_equal "new container capture", File.binread(destination)
assert File.exist?(File.join(control, "ack-#{token}"))
refute Dir.exist?(old_container)
@@ -288,7 +289,7 @@ class ScreenshotFramesTests < Minitest::Test
File.write(File.join(@root, "com.akinokaede.termind/.git"), "gitdir: fixture")
FileUtils.mkdir_p(File.join(@root, "fastlane"))
FileUtils.cp(File.expand_path("../../fastlane/Framefile.json", __dir__), File.join(@root, "fastlane"))
frame_template(File.join(@root, ".build/frameit-assets/iphone-18-pro-max.png"), 1470, 3000)
frame_template(File.join(@root, ".build/frameit-assets/iphone-18-pro.png"), 1350, 2760)
@templates = File.join(@root, "templates")
FileUtils.mkdir_p(@templates)
end
@@ -350,30 +351,34 @@ class ScreenshotFramesTests < Minitest::Test
def test_mac_composition_clips_corners_and_preserves_source_and_content
template = File.join(@root, "mac-frame.png")
runner = ScreenshotFrames.new({ mac_frame: template }, root: @root)
runner.command("magick", "-size", "4260x2620", "xc:none", "-fill", "none",
runner.command("magick", "-size", "3860x2320", "xc:none", "-fill", "none",
"-stroke", "black", "-strokewidth", "20",
"-draw", "roundrectangle 392,73,3867,2326,100,100", template)
"-draw", "roundrectangle 408,58,3451,2041,100,100", template)
source = File.join(@root, "capture.png")
output = File.join(@root, "composed.png")
runner.command("magick", "-size", "3024x1964", "xc:#327abe", source)
runner.command("magick", "-size", "3024x1964", "xc:#327abe", "-fill", "red",
"-draw", "point 1512,982", source)
original = Digest::SHA256.file(source).hexdigest
runner.compose_mac(source, output)
assert_equal [4260, 2620], FastImage.size(output)
assert_equal [3860, 2320], FastImage.size(output)
# Native screen pixels must land at the aperture offset without resampling.
assert_equal "255,0,0", runner.command("magick", output, "-format",
"%[fx:round(255*p{1930,1050}.r)],%[fx:round(255*p{1930,1050}.g)],%[fx:round(255*p{1930,1050}.b)]", "info:").strip
assert_equal original, Digest::SHA256.file(source).hexdigest
# The old rectangular placement leaked here, outside the curved bezel.
assert_equal "0", runner.command("magick", output, "-format", "%[fx:p{402,83}.a]", "info:").strip
assert_equal "1", runner.command("magick", output, "-format", "%[fx:p{2130,1200}.a]", "info:").strip
assert_equal "0", runner.command("magick", output, "-format", "%[fx:p{418,68}.a]", "info:").strip
assert_equal "1", runner.command("magick", output, "-format", "%[fx:p{1930,1160}.a]", "info:").strip
assert_equal "50,122,190", runner.command("magick", output, "-format",
"%[fx:round(255*p{2130,1200}.r)],%[fx:round(255*p{2130,1200}.g)],%[fx:round(255*p{2130,1200}.b)]", "info:").strip
"%[fx:round(255*p{1930,1160}.r)],%[fx:round(255*p{1930,1160}.g)],%[fx:round(255*p{1930,1160}.b)]", "info:").strip
end
def test_default_frames_only_include_website_used_scenes
jobs = ScreenshotFrames.new({}, root: @root).plan
assert_equal 21, jobs.size
refute jobs.any? { |job| job[:input].end_with?("/ipad/ssh.png", "/iphone/metrics.png") }
refute jobs.any? { |job| job[:input].end_with?("/ipad-15/ssh.png", "/iphone-6.3/metrics.png") }
explicit = ScreenshotFrames.new({ devices: "ipad", scenes: "ssh", locales: "en" }, root: @root).plan
assert_equal 1, explicit.size
assert explicit.first[:input].end_with?("/ipad/ssh.png")
assert explicit.first[:input].end_with?("/ipad-15/ssh.png")
end
def test_default_frames_fail_before_creating_outputs_when_submodule_is_missing
@@ -386,8 +391,8 @@ class ScreenshotFramesTests < Minitest::Test
def test_explicit_frame_paths_work_without_submodule_and_keep_source_bytes
FileUtils.remove_entry(File.join(@root, "com.akinokaede.termind"))
source = File.join(@root, "review-input/en/iphone/vault.png")
png(source, 1320, 2868)
source = File.join(@root, "review-input/en/iphone-6.3/vault.png")
png(source, 1206, 2622)
original_hash = Digest::SHA256.file(source).hexdigest
runner = ScreenshotFrames.new({ devices: "iphone", scenes: "vault", locales: "en",
input: "review-input", output: "review-output" }, root: @root)
@@ -396,21 +401,21 @@ class ScreenshotFramesTests < Minitest::Test
Frameit::FrameDownloader.stub(:templates_path, @templates) do
Frameit::FrameDownloader.stub(:new, downloader) do
runner.run do |options|
assert_equal "iPhone 18 Pro Max", options.fetch(:force_device_type)
assert_equal "iPhone 18 Pro", options.fetch(:force_device_type)
framefile = JSON.parse(File.read(File.join(options.fetch(:path), "Framefile.json")))
assert_equal({ "offset" => "+75+66", "width" => 1320 }, framefile.fetch("default").fetch("offset"))
png(File.join(options.fetch(:path), "iphone_framed.png"), 1470, 3000)
assert_equal({ "offset" => "+72+69", "width" => 1206 }, framefile.fetch("default").fetch("offset"))
png(File.join(options.fetch(:path), "iphone_framed.png"), 1350, 2760)
end
end
end
assert File.exist?(File.join(@root, "review-output/en/iphone/vault.png"))
assert File.exist?(File.join(@root, "review-output/en/iphone-6.3/vault.png"))
assert_equal original_hash, Digest::SHA256.file(source).hexdigest
refute Dir.exist?(File.join(@root, "com.akinokaede.termind"))
end
def test_rejects_pro_sources_before_calling_frameit
source = File.join(@root, "com.akinokaede.termind/assets/captures/en/iphone/vault.png")
png(source, 1206, 2622)
def test_rejects_pro_max_sources_before_calling_frameit
source = File.join(@root, "com.akinokaede.termind/assets/captures/en/iphone-6.3/vault.png")
png(source, 1320, 2868)
runner = ScreenshotFrames.new({ devices: "iphone", scenes: "vault", locales: "en" }, root: @root)
called = false
assert_raises(RuntimeError) { runner.run { called = true } }
@@ -461,12 +466,12 @@ class ScreenshotFramesTests < Minitest::Test
def test_missing_frameit_output_keeps_all_existing_outputs_and_sources
%w[en zh-hans].each do |locale|
png(File.join(@root, "com.akinokaede.termind/assets/captures/#{locale}/iphone/vault.png"), 1320, 2868)
png(File.join(@root, "com.akinokaede.termind/assets/captures/#{locale}/iphone-6.3/vault.png"), 1206, 2622)
end
destination = File.join(@root, "com.akinokaede.termind/assets/device_frames/en/iphone/vault.png")
destination = File.join(@root, "com.akinokaede.termind/assets/device_frames/en/iphone-6.3/vault.png")
FileUtils.mkdir_p(File.dirname(destination))
File.write(destination, "existing frame")
source = File.join(@root, "com.akinokaede.termind/assets/captures/en/iphone/vault.png")
source = File.join(@root, "com.akinokaede.termind/assets/captures/en/iphone-6.3/vault.png")
original_hash = Digest::SHA256.file(source).hexdigest
runner = ScreenshotFrames.new({ devices: "iphone", scenes: "vault", locales: "en,zh-Hans" }, root: @root)
downloader = Object.new
+10 -12
View File
@@ -6,8 +6,8 @@ require_relative "screenshots"
class ScreenshotFrames
DEVICES = %w[iphone iphone-duo-outer iphone-duo-inner ipad mac].freeze
CUSTOM_FRAMES = {
"iphone" => { name: "iPhone 18 Pro Max", size: [1470, 3000], offset: "+75+66", width: 1320,
color: "Silver", template: "iphone-18-pro-max" },
"iphone" => { name: "iPhone 18 Pro", size: [1350, 2760], offset: "+72+69", width: 1206,
color: "Silver", template: "iphone-18-pro" },
"iphone-duo-outer" => { name: "iPhone Duo Outer", size: [1574, 2194], offset: "+88+80", width: 1398, color: "Star White" },
"iphone-duo-inner" => { name: "iPhone Duo Inner", size: [2247, 3093], offset: "+120+120", width: 2007, color: "Star White" }
}.freeze
@@ -18,7 +18,7 @@ class ScreenshotFrames
def initialize(options = {}, root: Screenshots::ROOT)
@root = root
@mac_frame = File.expand_path(options[:mac_frame] || ".build/frameit-assets/macbook_pro_template.png", root)
@mac_frame = File.expand_path(options[:mac_frame] || ".build/frameit-assets/macbook-pro-14-template.png", root)
@ipad_frame = File.expand_path(options[:ipad_frame] || ".build/frameit-assets/ipad-pro-13-m5.png", root)
@custom_frames = CUSTOM_FRAMES.to_h do |device, specification|
option = device.tr("-", "_").concat("_frame").to_sym
@@ -35,7 +35,7 @@ class ScreenshotFrames
scenes = device == "mac" ? ["workspace"] : Screenshots::MOBILE_SCENES
selected = selector.selection(@scenes, WEBSITE_SCENES.fetch(device), scenes, "#{device} scenes")
selected.product(@locales).map do |scene, language|
relative = "#{language.downcase}/#{device}/#{scene}"
relative = "#{language.downcase}/#{Screenshots.asset_device_directory(device)}/#{scene}"
{ device: device, input: File.join(@input, "#{relative}.png"),
output: File.join(@output, "#{relative}.png") }
end
@@ -75,7 +75,7 @@ class ScreenshotFrames
raise "Import the official iPad Pro (M5) 13-inch Space Black portrait PNG first; " \
"see Scripts/ASSET_WORKFLOW.md or pass ipad_frame:/path/to/frame.png"
end
if @devices.include?("mac") && FastImage.size(@mac_frame) != [4260, 2620]
if @devices.include?("mac") && FastImage.size(@mac_frame) != [3860, 2320]
raise "Import the narrow MacBook Pro template first; see Scripts/ASSET_WORKFLOW.md " \
"or pass mac_frame:/path/to/frame.png"
end
@@ -156,15 +156,13 @@ class ScreenshotFrames
end
def compose_mac(source, output)
# The official M5 16-inch aperture is 3456x2234 after removing the
# template's 220px transparent top margin. Preserve the capture's aspect
# ratio and trim only 11px from its black full-screen camera strip.
# The official M5 14-inch aperture matches the 3024x1964 capture exactly.
# Only the template's 220px transparent top margin is removed in preparation.
Dir.mktmpdir("mac-screen-", File.dirname(output)) do |directory|
screen = File.join(directory, "screen.png")
command("magick", source, "-resize", "3456x", "-gravity", "south",
"-extent", "3456x2234", screen)
mask_screen(screen, @mac_frame, "+402+83")
command("magick", @mac_frame, "-colorspace", "sRGB", screen, "-gravity", "northwest", "-geometry", "+402+83",
FileUtils.cp(source, screen)
mask_screen(screen, @mac_frame, "+418+68")
command("magick", @mac_frame, "-colorspace", "sRGB", screen, "-gravity", "northwest", "-geometry", "+418+68",
"-compose", "DstOver", "-composite", output)
end
end
+9 -4
View File
@@ -9,6 +9,11 @@ require "securerandom"
class Screenshots
ROOT = File.expand_path("../..", __dir__)
ASSET_DIRECTORY = "com.akinokaede.termind".freeze
ASSET_DEVICE_DIRECTORIES = { "iphone" => "iphone-6.3", "ipad" => "ipad-15", "mac" => "mac-14" }.freeze
def self.asset_device_directory(device)
ASSET_DEVICE_DIRECTORIES.fetch(device, device)
end
def self.require_assets!(root)
directory = File.join(root, ASSET_DIRECTORY)
@@ -27,8 +32,8 @@ class Screenshots
}.freeze
MOBILE_SCENES = %w[vault sftp ssh metrics assistant].freeze
DEVICES = {
"iphone" => { name: "iPhone 18 Pro Max", type: "com.apple.CoreSimulator.SimDeviceType.iPhone-18-Pro-Max",
dimensions: [1320, 2868] },
"iphone" => { name: "iPhone 18 Pro", type: "com.apple.CoreSimulator.SimDeviceType.iPhone-18-Pro",
dimensions: [1206, 2622] },
"iphone-duo-outer" => { name: "iPhone Duo", type: "com.apple.CoreSimulator.SimDeviceType.iPhone-Duo",
dimensions: [1398, 2034], display: "primary" },
"iphone-duo-inner" => { name: "iPhone Duo", type: "com.apple.CoreSimulator.SimDeviceType.iPhone-Duo",
@@ -219,7 +224,7 @@ class Screenshots
command("sips", "-c", height.to_s, width.to_s, captured)
end
validate_dimensions(captured, native_size)
promote([[captured, "#{language.downcase}/mac/#{prefix == 'mac-overview' ? 'metrics_overview' : feature.tr('-', '_')}.png"]])
promote([[captured, "#{language.downcase}/#{self.class.asset_device_directory("mac")}/#{prefix == 'mac-overview' ? 'metrics_overview' : feature.tr('-', '_')}.png"]])
ensure
FileUtils.rm_f(xctestrun) if xctestrun
end
@@ -294,7 +299,7 @@ class Screenshots
command(*arguments, captured)
end
validate_dimensions(captured, DEVICES.fetch(device).fetch(:dimensions))
captures[feature] = [captured, "#{locale}/#{device}/#{feature}.png"]
captures[feature] = [captured, "#{locale}/#{self.class.asset_device_directory(device)}/#{feature}.png"]
tokens << token
FileUtils.touch(File.join(control, "ack-#{token}"))
end