Hammerspoon / hammerspoon

Staggeringly powerful macOS desktop automation with Lua
http://www.hammerspoon.org
MIT License
12.1k stars 582 forks source link

Additions to hs.screen:shotAsJPG() #1390

Closed latenitefilms closed 7 years ago

latenitefilms commented 7 years ago

It would be awesome if you could customise the output size and resolution (i.e. I want to output my 5K iMac screen to 1920x1080 @ 52dpi).

asmagill commented 7 years ago

What does hs.screen:snapshot():setSize{ w = 1920, h = 1080 }:saveToFile("/path/to/file.jpg", "jpg") give you?

latenitefilms commented 7 years ago

@asmagill - Yeah, I tried that.

Using:

hs.screen.primaryScreen():snapshot():setSize({ w = 1920, h = 1080 }):saveToFile("~/Desktop/test.jpg", "jpg")

...still returns an file that is the full resolution/size of the screen.

I assume because saveToFile "Saves image at its original size"?

asmagill commented 7 years ago

I'll have to look into it... setSize is supposed to return brand new image object (for backwards compatibility reasons) at the new size, so what finally makes it into saveToFile is no longer the original image...

I know setSize works when using the image object in menus, etc. but since saveToFile uses a bitmap representation, it may be bypassing some of the NSImage attributes that aren't applied directly to the source data but rather to how that source data is "shared" with code accessing it as an NSImage.

asmagill commented 7 years ago

Try pull #1373 with hs.screen:snapshot():setSize{ w = 1920, h = 1080 }:saveToFile("/path/to/file.jpg", "jpg") and see if that's better.

I'm not sure that we can easily get into modifying the images actual dpi without really delving into CGImage and/or CIImage/CIFilter territory, and while I'd like to sometime because there are some really cool things you can do with images in there, I don't see having the time in the near future.

(if anyone else wants to take this on, I made a stab at the beginning of a module for CIFilter awhile back that can be found at https://github.com/asmagill/hammerspoon_asm/blob/master/cifilter/ that you're more then welcome to start from or throw away entirely)

latenitefilms commented 7 years ago

Nope - no change unfortunately:

screen shot 2017-05-04 at 2 24 41 pm
asmagill commented 7 years ago

Retina display?

When I do the save specifying the size of 1920x1080, I get an image at 3456x2160 that is 729k When I do the save with no size method included, the image is at 6720x4200 that is 1.8 MB

e.g.

> hs.screen.primaryScreen():snapshot():setSize({ w = 1920, h = 1080 }):saveToFile("~/Desktop/test.jpg", "jpg")
true

> hs.screen.primaryScreen():snapshot():saveToFile("~/Desktop/test2.jpg", "jpg")
true

Let me see if we can remove the scaling factor easily...

latenitefilms commented 7 years ago

Yes, MacBook Pro (Retina, 15-inch, Early 2013).

Thanks so much for all your help @asmagill !

asmagill commented 7 years ago

Ok, try the updated pull now, the examples I used were:

hs.screen.mainScreen():snapshot():setSize({ w = 1920, h = 1080 }):saveToFile("~/Desktop/test.jpg", "jpg"),
hs.screen.mainScreen():snapshot():saveToFile("~/Desktop/test2.jpg", "jpg"),
hs.screen.mainScreen():snapshot():setSize({ w = 1920, h = 1080 }):saveToFile("~/Desktop/test3.jpg", true, "jpg"),
hs.screen.mainScreen():snapshot():saveToFile("~/Desktop/test4.jpg", true, "jpg")

and I got files basically as I expected (the third one was actually 1727x1080, but that's to be expected since setSize wasn't set to absolute so it scaled it proportionally to fit.)

latenitefilms commented 7 years ago

Sorry! I get this:

screen shot 2017-05-04 at 10 05 31 pm
asmagill commented 7 years ago

What line from the hs.image internal.m file is on the callback stack?

Don't have access to a 5k monitor, so not sure what kind of issues that might introduce memory wise... what does hs.inspect(hs.screen.mainScreen():currentMode()) report when that screen has the focused window? How large of a file (dimensions and size) does hs.screen.mainScreen():snapshot():saveToFile("path", "jpg") generate (i.e. no size changes, no pixel scaling)

latenitefilms commented 7 years ago

This is just on my laptop - not my iMac.

> hs.inspect(hs.screen.mainScreen():currentMode())
2017-05-05 10:14:19: -- Loading extension: inspect
2017-05-05 10:14:19: -- Loading extension: screen
{
  desc = "1680x1050@2x",
  h = 1050,
  scale = 2.0,
  w = 1680
}

When trying to run:

hs.screen.mainScreen():snapshot():setSize({ w = 1920, h = 1080 }):saveToFile("~/Desktop/test.jpg", "jpg"),
hs.screen.mainScreen():snapshot():saveToFile("~/Desktop/test2.jpg", "jpg"),
hs.screen.mainScreen():snapshot():setSize({ w = 1920, h = 1080 }):saveToFile("~/Desktop/test3.jpg", true, "jpg"),
hs.screen.mainScreen():snapshot():saveToFile("~/Desktop/test4.jpg", true, "jpg")

I get:

screen shot 2017-05-05 at 10 15 40 am

asmagill commented 7 years ago

I can confirm it will crash with this when run from within XCode.

I don't know enough about XCode and how it changes/manipulates things to even begin to try and debug this, so let me know if it works for you when running as a standalone application.

latenitefilms commented 7 years ago

Ah, right, no worries. I know absolutely nothing about Xcode - so didn't even think to try it outside of Xcode.

It no longer crashes!

test.jpg = 3840x2160 @ 144 dpi test2.jpg = 3840x2160 @ 144 dpi test3.jpg = 1920x1080 @ 72 dpi test4.jpg = 1920x1080 @ 72 dpi

Thanks @asmagill !!

asmagill commented 7 years ago

If someone more familiar with how XCode modifies the environment when a program is being run from within it wants to tackle the crash described above, please feel free to reopen this (and describe the issue so we can all learn from it!).

cmsj commented 7 years ago

I haven't tried to reproduce this yet, but note that it's not a crash, it's detecting that it's being run in a debugger and is intentionally popping out control to the debugger because it is suspicious that something just tried to malloc() 134MB.

cmsj commented 7 years ago

While 134MB does seem fairly crazy, if you look in the stacktrace, it's fetching a TIFF representation of the image.

Assuming you're running your 5K iMac at the native @2x resolution (2560x1440) the actual framebuffer is 5120x2880, which at 32bit would make for 59MB of raw image data, but it's not 32bit, because the display is 10bits per channel (not 100% sure if the alpha channel in a screenshot would also get 10bits) which puts the image data closer to 74MB and that gets us within 2x of the malloc() size.

All of that is to say, while it's perhaps surprising to see 134MB allocated for a screenshot, it's perhaps not unreasonable :)