device-shots 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/index.js +38 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -208,17 +208,17 @@ Both platforms get framed automatically after capture (disable with `--no-frame`
|
|
|
208
208
|
|
|
209
209
|
Uses [device-frames-core](https://pypi.org/project/device-frames-core/) to wrap screenshots in realistic Apple device frames (iPhone, iPad). The device model is auto-detected from the screenshot resolution. Requires Python 3 (venv is managed automatically).
|
|
210
210
|
|
|
211
|
-
### Android — Black
|
|
211
|
+
### Android — Black frame with rounded corners
|
|
212
212
|
|
|
213
|
-
Uses ImageMagick to
|
|
213
|
+
Uses ImageMagick to clip the screenshot to rounded corners (overflow hidden) and place it on a black rounded rectangle with uniform padding on all sides. Dimensions scale proportionally to the screenshot:
|
|
214
214
|
|
|
215
215
|
| Property | Value |
|
|
216
216
|
|----------|-------|
|
|
217
|
-
|
|
|
218
|
-
| Inner corner radius | ~4
|
|
219
|
-
| Outer corner radius | inner radius +
|
|
217
|
+
| Padding | ~2.5% of image width (equal on all sides) |
|
|
218
|
+
| Inner corner radius | ~4% of image width |
|
|
219
|
+
| Outer corner radius | inner radius + padding |
|
|
220
220
|
|
|
221
|
-
For a 1080px wide screenshot, this produces
|
|
221
|
+
For a 1080px wide screenshot, this produces ~27px padding with ~43px inner rounded corners.
|
|
222
222
|
|
|
223
223
|
## License
|
|
224
224
|
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
existsSync as existsSync4,
|
|
12
12
|
mkdirSync,
|
|
13
13
|
readdirSync as readdirSync2,
|
|
14
|
-
|
|
14
|
+
copyFileSync,
|
|
15
|
+
unlinkSync,
|
|
15
16
|
readFileSync,
|
|
16
17
|
writeFileSync
|
|
17
18
|
} from "fs";
|
|
@@ -138,6 +139,9 @@ function getIosScreenSize(width, height) {
|
|
|
138
139
|
}
|
|
139
140
|
function getAndroidScreenSize(width, height) {
|
|
140
141
|
const shorter = Math.min(width, height);
|
|
142
|
+
const longer = Math.max(width, height);
|
|
143
|
+
const ratio = longer / shorter;
|
|
144
|
+
if (ratio >= 1.7) return "phone";
|
|
141
145
|
if (shorter >= 1800) return "tablet-10";
|
|
142
146
|
if (shorter >= 1200) return "tablet-7";
|
|
143
147
|
return "phone";
|
|
@@ -518,20 +522,20 @@ async function frameAndroidScreenshot(inputPath, outputPath) {
|
|
|
518
522
|
if (!match) return false;
|
|
519
523
|
const width = parseInt(match[1], 10);
|
|
520
524
|
const height = parseInt(match[2], 10);
|
|
521
|
-
const
|
|
522
|
-
const innerRadius = Math.round(width * 0.
|
|
523
|
-
const outerRadius = innerRadius +
|
|
524
|
-
const totalW = width +
|
|
525
|
-
const totalH = height +
|
|
525
|
+
const padding = Math.round(width * 0.025);
|
|
526
|
+
const innerRadius = Math.round(width * 0.04);
|
|
527
|
+
const outerRadius = innerRadius + padding;
|
|
528
|
+
const totalW = width + padding * 2;
|
|
529
|
+
const totalH = height + padding * 2;
|
|
526
530
|
try {
|
|
527
531
|
await runOrFail("magick", [
|
|
528
|
-
// Create black rounded rectangle background
|
|
532
|
+
// 1. Create black rounded rectangle background (outer frame)
|
|
529
533
|
"-size",
|
|
530
534
|
`${totalW}x${totalH}`,
|
|
531
535
|
"xc:none",
|
|
532
536
|
"-draw",
|
|
533
537
|
`fill black roundrectangle 0,0 ${totalW - 1},${totalH - 1} ${outerRadius},${outerRadius}`,
|
|
534
|
-
// Load screenshot and
|
|
538
|
+
// 2. Load screenshot and clip to rounded corners (overflow hidden)
|
|
535
539
|
"(",
|
|
536
540
|
inputPath,
|
|
537
541
|
"-alpha",
|
|
@@ -549,7 +553,7 @@ async function frameAndroidScreenshot(inputPath, outputPath) {
|
|
|
549
553
|
"DstIn",
|
|
550
554
|
"-composite",
|
|
551
555
|
")",
|
|
552
|
-
//
|
|
556
|
+
// 3. Place rounded screenshot centered on the black frame
|
|
553
557
|
"-gravity",
|
|
554
558
|
"center",
|
|
555
559
|
"-compose",
|
|
@@ -705,7 +709,7 @@ Detected ${devices.length} device(s) with ${bundleIdDisplay}:`)
|
|
|
705
709
|
const captured = [];
|
|
706
710
|
for (const device of devices) {
|
|
707
711
|
const filename = `${screenshotName}.png`;
|
|
708
|
-
const tmpPath = join4(tmpDir, `${device.platform}_${device.screenSize}_${filename}`);
|
|
712
|
+
const tmpPath = join4(tmpDir, `${device.platform}_${device.screenSize}_${device.safeName}_${filename}`);
|
|
709
713
|
const icon = device.platform === "ios" ? "iOS" : "Android";
|
|
710
714
|
const s = ora(
|
|
711
715
|
`${icon}: Capturing from ${device.displayName}...`
|
|
@@ -737,10 +741,25 @@ Detected ${devices.length} device(s) with ${bundleIdDisplay}:`)
|
|
|
737
741
|
s.fail(`${icon}: Failed to capture from ${device.displayName}`);
|
|
738
742
|
}
|
|
739
743
|
}
|
|
744
|
+
const movedBuckets = /* @__PURE__ */ new Set();
|
|
740
745
|
for (const file of captured) {
|
|
746
|
+
const bucketKey = `${file.platform}/${file.screenSize}`;
|
|
747
|
+
if (movedBuckets.has(bucketKey)) {
|
|
748
|
+
try {
|
|
749
|
+
unlinkSync(file.tmpPath);
|
|
750
|
+
} catch {
|
|
751
|
+
}
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
movedBuckets.add(bucketKey);
|
|
741
755
|
const destDir = join4(outputDir, file.platform, file.screenSize);
|
|
742
756
|
mkdirSync(destDir, { recursive: true });
|
|
743
|
-
|
|
757
|
+
const destPath = join4(destDir, file.filename);
|
|
758
|
+
copyFileSync(file.tmpPath, destPath);
|
|
759
|
+
try {
|
|
760
|
+
unlinkSync(file.tmpPath);
|
|
761
|
+
} catch {
|
|
762
|
+
}
|
|
744
763
|
}
|
|
745
764
|
updateMetadata(outputDir, devices);
|
|
746
765
|
if (iosDevices.length > 0) {
|
|
@@ -749,12 +768,13 @@ Detected ${devices.length} device(s) with ${bundleIdDisplay}:`)
|
|
|
749
768
|
for (const device of androidDevices) {
|
|
750
769
|
await clearAndroidDemoMode(device.captureId);
|
|
751
770
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
)
|
|
757
|
-
|
|
771
|
+
const skippedDupes = captured.length - movedBuckets.size;
|
|
772
|
+
let msg = `
|
|
773
|
+
Captured ${movedBuckets.size} screenshot(s) as '${screenshotName}'.`;
|
|
774
|
+
if (skippedDupes > 0) {
|
|
775
|
+
msg += ` (${skippedDupes} duplicate bucket(s) skipped)`;
|
|
776
|
+
}
|
|
777
|
+
console.log(pc.green(msg));
|
|
758
778
|
if (shouldFrame) {
|
|
759
779
|
if (iosDevices.length > 0) {
|
|
760
780
|
console.log("");
|
|
@@ -857,7 +877,7 @@ async function frameCommand(dir, options) {
|
|
|
857
877
|
var program = new Command();
|
|
858
878
|
program.name("device-shots").description(
|
|
859
879
|
"Capture and frame mobile app screenshots from iOS simulators and Android emulators"
|
|
860
|
-
).version("0.4.
|
|
880
|
+
).version("0.4.2");
|
|
861
881
|
program.command("capture").description("Capture screenshots from running devices").argument("[name]", "Screenshot name").option("-b, --bundle-id <id>", "App bundle ID").option("-o, --output <dir>", "Output directory").option("-p, --platform <platform>", "ios, android, or both").option("--no-frame", "Skip framing after capture").option("--time <time>", "Status bar time", "9:41").action(async (name, opts) => {
|
|
862
882
|
await captureCommand({ name, ...opts });
|
|
863
883
|
});
|