Copy link to clipboard
Copied
Hi, after this last update about 2 weeks ago, Photoshop started making my JPG's huge when I save them out from my PSD files. I'm mostly designing website banners and emails currently, where the PSD's themselves are well under 3MB, yet when I do 'Save As' to make a JPG when I'm done, no matter what quality I select on the JPG slider, the JPG file size ends up being 16-24MB! And these are small, RGB, 72dpi, 600px max width, and 200-2000px height, which normally save out to JPG's well under 100KB for the banners, and under 1MB for the emails. I've been able to work around it by doing the Export/Save for Web legacy option, but it's been pretty annoying to not just be able to quickly save a JPG like I have been for over 20 years, haha. So, I just wanted to throw this out there and see if it's a known bug that just needs fixed, or if anyone knows of a weird/random setting that I know nothing about that could fix this? I kind of doubt it's a settings issue, because I can't think of a scenario where anyone would ever need a 600px X 200px JPG to be over 16MB and unusable for the web. Thanks for any help on this!
My system: Lenovo - Intel Core i7 8700 CPU @ 3.20GHz, 32GB RAM, 64-bit, Windows 10 Pro.
When you copy layers between documents, Photoshop writes a specific set of metadata associated with the original document. On the one hand, this is useful and allows you to track the chains of copying layers, and on the other hand (when you regularly copy the same set of layers across multiple documents), this can lead to a increase in the amount of metadata in each file. The maximum size of metadata in one file that I met was 97 (!!!) megabytes 😱
There are three options:
Copy link to clipboard
Copied
I suspect it's all Metadata. If you use Export As or Save for Web choose None for metadata, or just Copyright, are the files the size you expect?
Copy link to clipboard
Copied
When you copy layers between documents, Photoshop writes a specific set of metadata associated with the original document. On the one hand, this is useful and allows you to track the chains of copying layers, and on the other hand (when you regularly copy the same set of layers across multiple documents), this can lead to a increase in the amount of metadata in each file. The maximum size of metadata in one file that I met was 97 (!!!) megabytes 😱
There are three options:
You can find solution in Metadata Bloat - Abnormally large PSD files when I save them, but when I faced the same problem and realized that I have a huge amount of files with junk metadata, I decided to write a script to batch delete such metadata (yes, you can do the same with imageMagik or exifTool, but I just love scripts).
Perhaps it helps you to cleanup metadata in your templates once and forever:
#target photoshop
var allFiles = [], junkedFiles = [], filter;
var w = buildWindow(), result = w.show();
if (result != 2) {
app.doProgress("", "removeMetadata(result)")
alert("Success!")
}
function buildWindow() {
var w = new Window("dialog")
w.text = "Batch ancestors remover"
w.orientation = "column"
w.alignChildren = ["fill", "top"]
var pnSource = w.add("panel")
pnSource.text = "Source:"
pnSource.orientation = "column"
pnSource.alignChildren = ["left", "top"]
pnSource.alignment = ["fill", "top"]
var grBrowse = pnSource.add("group")
grBrowse.orientation = "row"
grBrowse.alignChildren = ["left", "center"]
var etPath = grBrowse.add('edittext {properties: {readonly: true}}')
etPath.preferredSize.width = 300
var bnBrowse = grBrowse.add("button", undefined, "Browse...")
var chSubfolders = pnSource.add("checkbox")
chSubfolders.text = "get files from subfolders"
chSubfolders.value = true
var stTotalFiles = pnSource.add("statictext")
var pnTarget = w.add("panel")
pnTarget.text = "Document ancestors:"
pnTarget.orientation = "column"
pnTarget.alignChildren = ["left", "top"]
pnTarget.alignment = ["fill", "top"]
var stTotalMetadata = pnTarget.add("statictext")
var grFilter = pnTarget.add("group")
grFilter.orientation = "row"
grFilter.alignChildren = ["left", "center"]
var stFilter = grFilter.add("statictext")
stFilter.text = "process the following file types:"
var dlFilter = grFilter.add("dropdownlist")
dlFilter.preferredSize.width = 100
var stSize = pnTarget.add("statictext")
var grExport = pnTarget.add("group")
grExport.orientation = "row"
grExport.alignChildren = ["left", "center"]
grExport.alignment = ["center", "top"]
var bnExport = grExport.add("button", undefined, "Export statistics to CSV")
var grButtons = w.add("group")
grButtons.orientation = "row"
grButtons.alignChildren = ["center", "center"]
var ok = grButtons.add("button", undefined, "Remove ancestors metadata", { name: "ok" })
var cancel = grButtons.add("button", undefined, "Cancel", { name: "cancel" })
stTotalFiles.setText = function (s) { this.text = "total number of files: " + s }
stTotalMetadata.setText = function (s) { this.text = "number of files with ancestors records: " + s }
stSize.setText = function (s) { this.text = "approximate estimate of the occupied size: " + s + " Mb" }
chSubfolders.onClick = function (s) { enumFiles(etPath.text) }
bnBrowse.onClick = function () {
var fol = new Folder()
currentFolder = fol.selectDlg()
enumFiles(currentFolder)
}
dlFilter.onChange = function () {
if (!w.visible) return
filter = this.selection.text
if (junkedFiles.length > 0) {
var len = junkedFiles.length,
mCounter = 0,
fCounter = 0
for (var i = 0; i < len; i++) {
if (isFileOneOfThese(junkedFiles[i].file, filter)) {
mCounter += junkedFiles[i].junkRecords
fCounter++
}
}
stTotalMetadata.setText(fCounter)
stSize.setText(Number(mCounter / 10000 * 0.7).toFixed(4))
}
}
bnExport.onClick = function () {
var fle = new File("DocumentAncestors"),
pth = fle.saveDlg("Save list of files to disk", "*.csv")
try {
if (pth) {
pth.open("w", "TEXT", "????")
var len = junkedFiles.length,
output = [];
for (var i = 0; i < len; i++) {
if (isFileOneOfThese(junkedFiles[i].file, filter)) {
var msg = junkedFiles[i].junkRecords == 0 ? "" : ";" + junkedFiles[i].junkRecords
output.push(junkedFiles[i].file.toString() + msg)
}
}
pth.write(output.join('\n'))
pth.close
alert("Success!")
}
} catch (e) { alert(e, "", 1) }
}
ok.onClick = function () {
msg = "Select processing option:\n\n\[YES] - remove metadata from files without opening them in Photoshop\n(faster, file resizing not guaranteed)\
\n[NO] - open files in Photoshop and remove metadata \n(slower, file size will be updated)"
if (confirm(msg)) { w.close(1) } else { w.close(-1) }
}
w.onShow = function () {
stTotalFiles.size.width = stSize.size.width = stTotalMetadata.size.width = 300
stTotalFiles.setText(0)
stTotalMetadata.setText(0)
pnTarget.enabled = ok.enabled = false
dlFilter.add("item", "all files"); dlFilter.selection = 0
}
function enumFiles(currentFolder) {
if (currentFolder) {
allFiles = []
etPath.text = (new Folder(currentFolder)).fsName
findAllFiles(etPath.text, allFiles, chSubfolders.value)
var len = allFiles.length
stTotalFiles.setText(len)
countMetadata = len > 100 ? confirm("Selected directory contains " + len + " files.\nTry to calculate junked metadata size? (this may take several minutes)") : true
stSize.visible = countMetadata
app.doProgress("", "checkMetadata (countMetadata)")
}
}
function checkMetadata(countMetadata) {
var len = allFiles.length
if (len > 0) {
if (ExternalObject.AdobeXMPScript == undefined) ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript")
junkedFiles = []
for (var i = 0; i < len; i++) {
app.updateProgress(i + 1, len)
app.changeProgressText(allFiles[i])
try {
var xmpFile = new XMPFile(allFiles[i], XMPConst.UNKNOWN, XMPConst.OPEN_FOR_READ),
xmp = xmpFile.getXMP();
if (xmp.doesPropertyExist(XMPConst.NS_PHOTOSHOP, "DocumentAncestors")) {
var shift = 0
if (countMetadata) {
do {
shift++
if (!xmp.doesArrayItemExist(XMPConst.NS_PHOTOSHOP, "DocumentAncestors", shift)) break;
} while (true)
}
junkedFiles.push({ file: allFiles[i], junkRecords: shift })
}
} catch (e) { }
}
if (junkedFiles.length > 0) {
pnTarget.enabled = ok.enabled = true
dlFilter.removeAll()
var ext = buildShortcutList(junkedFiles)
for (var i = 0; i < ext.length; i++) dlFilter.add("item", ext[i])
dlFilter.selection = 0
} else pnTarget.enabled = ok.enabled = false
}
}
return w
}
function findAllFiles(srcFolder, destArray, useSubfolders) {
var fileFolderArray = Folder(srcFolder).getFiles();
var subfolderArray = []
for (var i = 0; i < fileFolderArray.length; i++) {
var fileFoldObj = fileFolderArray[i];
if (fileFoldObj instanceof File) {
if (!fileFoldObj.hidden) destArray.push(fileFoldObj.fsName)
} else if (useSubfolders) {
subfolderArray.push(fileFoldObj)
}
}
if (useSubfolders) {
for (var i = 0; i < subfolderArray.length; i++) findAllFiles(subfolderArray[i], destArray, useSubfolders)
}
}
function buildShortcutList(srcArray) {
var typeObject = {}, len = srcArray.length
for (var i = 0; i < len; i++) {
var fle = srcArray[i].file.toUpperCase()
var lastDot = fle.lastIndexOf(".")
if (lastDot == -1) continue;
var extension = fle.substr(lastDot + 1, fle.length - lastDot)
typeObject[extension] = "ok"
}
var reflect = typeObject.reflect.properties
var output = ["all files"]
len = reflect.length
for (var i = 0; i < len; i++) {
if (typeObject[reflect[i].name] == "ok") output.push(reflect[i].name)
}
return output
}
function isFileOneOfThese(fileName, ext) {
if (ext == "all files") return true
var fle = fileName.toString().toUpperCase()
var lastDot = fle.lastIndexOf(".")
if (lastDot == -1) return false
if (fle.substr(lastDot + 1, fle.length - lastDot) == ext) return true
return false
}
function removeMetadata(mode) {
if (ExternalObject.AdobeXMPScript == undefined) ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript")
var len = junkedFiles.length, target = [];
for (var i = 0; i < len; i++) {
if (isFileOneOfThese(junkedFiles[i].file, filter)) {
target.push(junkedFiles[i].file)
}
}
len = target.length
switch (mode) {
case 1:
for (var i = 0; i < len; i++) {
app.updateProgress(i + 1, len)
app.changeProgressText(target[i])
try {
var xmpFile = new XMPFile(target[i], XMPConst.UNKNOWN, XMPConst.OPEN_FOR_UPDATE)
var xmp = xmpFile.getXMP()
if (xmp.doesPropertyExist(XMPConst.NS_PHOTOSHOP, "DocumentAncestors")) {
xmp.deleteProperty(XMPConst.NS_PHOTOSHOP, "DocumentAncestors")
if (xmpFile.canPutXMP(xmp)) xmpFile.putXMP(xmp)
}
xmpFile.closeFile(XMPConst.CLOSE_UPDATE_SAFELY)
} catch (e) { }
}
break;
case -1:
for (var i = 0; i < len; i++) {
try {
app.updateProgress(i + 1, len)
app.changeProgressText(target[i])
app.open(File(target[i]))
var xmp = new XMPMeta(app.activeDocument.xmpMetadata.rawData)
if (xmp.doesPropertyExist(XMPConst.NS_PHOTOSHOP, "DocumentAncestors")) {
xmp.deleteProperty(XMPConst.NS_PHOTOSHOP, "DocumentAncestors")
app.activeDocument.xmpMetadata.rawData = xmp.serialize()
}
closeDocument(true)
} catch (e) { }
}
break;
}
}
function closeDocument(save) {
save = save != true ? s2t("no") : s2t("yes")
var desc = new ActionDescriptor();
desc.putEnumerated(s2t("saving"), s2t("yesNo"), save)
executeAction(s2t("close"), desc, DialogModes.NO)
function s2t(s) { return stringIDToTypeID(s) }
}
* save the code into a text file, change the file extension to jsx and run it, or drag it into the photoshop window.
Copy link to clipboard
Copied
This worked perfectly! Thank you SO much!
Copy link to clipboard
Copied
I tried this and I get a error
Error 8: Syntax error.
Line: 1 -> {\rtf1\ansi\ansicpg1252\cocoartf2580
What am I doing wrong?
Copy link to clipboard
Copied
@carolynf83417432 wrote:
I tried this and I get a error
Error 8: Syntax error.
Line: 1 -> {\rtf1\ansi\ansicpg1252\cocoartf2580What am I doing wrong?
You saved the file as a rich text format RTF file, not as plain text.
https://prepression.blogspot.com/2017/11/downloading-and-installing-adobe-scripts.html
Copy link to clipboard
Copied
The script works nicely. Thank you!
A couple of bugs:
Tried on macOs High Sierra 10.13.6 (17G14042) and Adobe Photoshop 2020 21.2.4.
Copy link to clipboard
Copied
Why only PSD and TIFF? Is there a way to add support for PNG and JPEG (the latter, without incurring in recompression so to be lossless)?
Copy link to clipboard
Copied
Excellent Jazz-y, I have added your script to the other solutions on this topic at my blog:
https://prepression.blogspot.com/2017/06/metadata-bloat-photoshopdocumentancestors.html
Copy link to clipboard
Copied
Thanks jazz-y! That video says abnormally large PSD files. My PSD files are actually fine and still as small as they should be. The bloated, massive file size is the JPG I get shen I do a normal 'save a copy'. Will that script still apply to shrinking the JPG's, or is it just a PSD thing. Either way, thank you so much!
Copy link to clipboard
Copied
@Travis23498974xyk2 wrote:
Thanks jazz-y! That video says abnormally large PSD files. My PSD files are actually fine and still as small as they should be. The bloated, massive file size is the JPG I get shen I do a normal 'save a copy'. Will that script still apply to shrinking the JPG's, or is it just a PSD thing. Either way, thank you so much!
The excessive photoshop:DocumentAncestors metadata is in the source files, however, you may not notice it as you expect larger file sizes with the lossless compression used in PSD/PSB files. You certainly notice it in derivative files such as JPEG where the lossy compression leads to different expectations for file size. If you don't remove the excessive metadata at the source (PSD) it will always be there when you Save As a Copy.
P.S. As my blogpost notes, this can affect many different file types and once it makes its way into InDesign and PDF files it is harder to remove. Best to remove it at the source.
Copy link to clipboard
Copied
Thanks Stephen! I totally get what you're saying. The weird part to me is that my PSD's in question are around 2MB (as they have been prior to this issue), but the JPG's I save out are over 20MB. I'm just not understanding how there would be 18GB of extra metadata present in the JPG, but not in the PSD that made it.
Copy link to clipboard
Copied
No, that doesn't make sense (I presume that you mean 18MB and not 18GB).
Unless I missed a post, it has just been conjecture so far. If you go to the PSD and use File > File Info and then select the raw data tab, do you see metadata or a message that metadata can't be displayed?
Copy link to clipboard
Copied
Oops! Sorry for the typo. Yes, I meant MB.
I just went to the Raw Data tab and it says "Cannot display raw metadata, contents too large"
Copy link to clipboard
Copied
Sounds like ancestor bloat!
Copy link to clipboard
Copied
Thanks Jeffrey Tranberry! When I use Export, Save for Web (legacy), the JPG file size is nice and small like it should be, whether I tell it no metadata or not (doesn't seem to have any effect either way on these). That's been my work around. The issue just happens when I try to do my normal 'save a copy' to create my JPG, like I've always done. I'd just like to be able to get back to doing the quicker 'save a copy' to create my JPG's like my workflow always has been.
Copy link to clipboard
Copied
The issue just happens when I try to do my normal 'save a copy' to create my JPG, like I've always done.
By @Travis23498974xyk2
==========
SaveAs or Save as Copy is for PRINT projects where file size is not a concern.
The preferred workflow for web images is to Export. In older versions of PS, we previously used Export > Legacy Save for Web. However, I never use that anymore due to it's advanced age -- except for animated GIFs.
Copy link to clipboard
Copied
Thanks Nancy!
I understand that it works for print projects, but Save As and Save as Copy has always worked beautifully for making JPG's as well. I never had any problem with this through all fo the versions of Photoshop since 2001, so I've continued doing it this way ever since then.
Copy link to clipboard
Copied
I understand. My point is Photoshop has many ways to skin the proverbial cat. But some workflows are better than others. I've had to change workflows dozens of times since I first began using PS. It comes with the territory.
Copy link to clipboard
Copied
I totally get that! I've defintely had to change my workflows several times with various apps over the years, including this one. I was just looking for some insight as to why all of the sudden the previous was got wonky 🙂
Copy link to clipboard
Copied
@Travis23498974xyk2 PSD on the video is just the files that were at hand.
The script works with most image file formats supported by Photoshop. Since I process my own files with it, I tried to make sure that its work was safe for images.
The peculiarity of this type of metadata is that they do not appear on their own, but are the result of transferring layers from one file to another. Even if the PSD size seems normal to you, it is better to check if it is the source of an abnormally large number of records of this type.
If you have concerns about the destructiveness of the script, you can first try it on copies of your files.
* the estimate of the size of the records that the script shows is approximate and does not take into account the actual content of the records. However, in most cases it is close to the actual one.
Copy link to clipboard
Copied
On my tests, in a folder with PSD, TIFF, JPEG and PNG all having DocumentAncestors metadata, it only detects it on PSD and TIF files (“total number of files” does see the JPEG and PNG files, but ”number of files with ancestors records” excludes them and so does pressing “Remove ancestors metadata”).
Copy link to clipboard
Copied
I tried looking into why the script skips JPEGs. It is strange because while the XMP I get by going to menu File ▸ Document information does have hundreds of DocumentAncestors tags:
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 6.0-c002 79.164460, 2020/05/12-16:04:17 ">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
xmlns:exif="http://ns.adobe.com/exif/1.0/">
<xmp:CreatorTool>Adobe Photoshop CC 2014 (Macintosh)</xmp:CreatorTool>
<xmp:CreateDate>2015-06-04T17:33+02:00</xmp:CreateDate>
<xmp:ModifyDate>2022-01-07T13:38:55+01:00</xmp:ModifyDate>
<xmp:MetadataDate>2022-01-07T13:38:55+01:00</xmp:MetadataDate>
<dc:format>application/vnd.adobe.photoshop</dc:format>
<photoshop:ColorMode>4</photoshop:ColorMode>
<photoshop:ICCProfile>SWOP2006_Coated3v2</photoshop:ICCProfile>
<photoshop:DocumentAncestors>
<rdf:Bag>
<rdf:li>…</rdf:li>
…
</rdf:Bag>
</photoshop:DocumentAncestors>
<xmpMM:InstanceID>xmp.iid:e94b2ee8-ab5c-4adb-9a6d-eb8bca3607bd</xmpMM:InstanceID>
<xmpMM:DocumentID>adobe:docid:photoshop:12d11eb3-707b-1178-a7ba-956b10462bd9</xmpMM:DocumentID>
<xmpMM:OriginalDocumentID>xmp.did:02693409-d599-7242-b3ed-869488ddf71e</xmpMM:OriginalDocumentID>
<xmpMM:History>
<rdf:Seq>
<rdf:li rdf:parseType="Resource">
<stEvt:action>created</stEvt:action>
<stEvt:instanceID>xmp.iid:02693409-d599-7242-b3ed-869488ddf71e</stEvt:instanceID>
<stEvt:when>2015-06-04T17:33+02:00</stEvt:when>
<stEvt:softwareAgent>Adobe Photoshop CC 2014 (Windows)</stEvt:softwareAgent>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<stEvt:action>saved</stEvt:action>
<stEvt:instanceID>xmp.iid:8608eb2a-319c-9f40-8a80-d13e1cf8ef86</stEvt:instanceID>
<stEvt:when>2015-06-05T23:06:20+02:00</stEvt:when>
<stEvt:softwareAgent>Adobe Photoshop CC 2014 (Windows)</stEvt:softwareAgent>
<stEvt:changed>/</stEvt:changed>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<stEvt:action>saved</stEvt:action>
<stEvt:instanceID>xmp.iid:e94b2ee8-ab5c-4adb-9a6d-eb8bca3607bd</stEvt:instanceID>
<stEvt:when>2022-01-07T13:38:55+01:00</stEvt:when>
<stEvt:softwareAgent>Adobe Photoshop 22.1 (Macintosh)</stEvt:softwareAgent>
<stEvt:changed>/</stEvt:changed>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<stEvt:action>converted</stEvt:action>
<stEvt:parameters>from application/vnd.adobe.photoshop to image/jpeg</stEvt:parameters>
</rdf:li>
</rdf:Seq>
</xmpMM:History>
<tiff:Orientation>1</tiff:Orientation>
<tiff:XResolution>3000000/10000</tiff:XResolution>
<tiff:YResolution>3000000/10000</tiff:YResolution>
<tiff:ResolutionUnit>2</tiff:ResolutionUnit>
<exif:ColorSpace>65535</exif:ColorSpace>
<exif:PixelXDimension>4784</exif:PixelXDimension>
<exif:PixelYDimension>1772</exif:PixelYDimension>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
if I dump the xml captured by the script to the console with `$.writeln(xmp.serialize())` in the line after `
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 6.0-c002 79.164460, 2020/05/12-16:04:17 ">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
xmlns:exif="http://ns.adobe.com/exif/1.0/">
<tiff:Orientation>1</tiff:Orientation>
<tiff:XResolution>300/1</tiff:XResolution>
<tiff:YResolution>300/1</tiff:YResolution>
<tiff:ResolutionUnit>2</tiff:ResolutionUnit>
<xmp:CreatorTool>Adobe Photoshop 22.1 (Macintosh)</xmp:CreatorTool>
<xmp:ModifyDate>2022-01-07T13:38:55</xmp:ModifyDate>
<exif:ColorSpace>65535</exif:ColorSpace>
<exif:PixelXDimension>4784</exif:PixelXDimension>
<exif:PixelYDimension>1772</exif:PixelYDimension>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
Copy link to clipboard
Copied
The script is not limited by file types. It tries to open ALL files and check if they have a "DocumentAncestors" section in the XMPConst.NS_PHOTOSHOP section. If there is, then the file is included in the list for deleting metadata, if not, it is ignored.
Honestly - I wrote it a long time ago and at that moment there was no way to test it on MacOs. Now I have checked its work on Catalina and CC2020 - all DocumentAncestors have been removed from a fairly large selection of files of different formats.
Perhaps there are problems of a different nature, not related to the script code. Can you attach multiple files that the script can't remove data from? You can resize it down to 1x1 px and fill it with white color - the metadata won't be affected.
Copy link to clipboard
Copied
Here is one such JPEG. In Photoshop, menu File ▸ Document info ▸ Advanced reports DocumentAncestors and other tags with the photoshop namespace. The script, conversely, seemingly sees no tags with the photoshop namespace (when checked with `$.writeln(xmp.serialize())`) and therefore keeps them.
One detail to note is that this JPEG was generated with ImageMagick (7.0.11-14 Q16 x86_64 2021-05-31 installed via homebrew) from a PSD generated with Photoshop (`mogrify -format jpg image.psd[0]`).