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.
Files changed (3) hide show
  1. package/README.md +6 -6
  2. package/dist/index.js +38 -18
  3. 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 border with rounded corners
211
+ ### Android — Black frame with rounded corners
212
212
 
213
- Uses ImageMagick to add a black bezel-like border with rounded corners. Dimensions scale proportionally to the screenshot:
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
- | Border width | ~1.8% of image width |
218
- | Inner corner radius | ~4.5% of image width |
219
- | Outer corner radius | inner radius + border width |
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 a ~19px border with ~49px rounded corners.
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
- renameSync,
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 borderWidth = Math.round(width * 0.018);
522
- const innerRadius = Math.round(width * 0.045);
523
- const outerRadius = innerRadius + borderWidth;
524
- const totalW = width + borderWidth * 2;
525
- const totalH = height + borderWidth * 2;
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 round its corners
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
- // Composite screenshot centered on black background
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
- renameSync(file.tmpPath, join4(destDir, file.filename));
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
- console.log(
753
- pc.green(
754
- `
755
- Captured ${captured.length} screenshot(s) as '${screenshotName}'.`
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.0");
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "device-shots",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Capture and frame mobile app screenshots from iOS simulators and Android emulators",
5
5
  "type": "module",
6
6
  "bin": {