Copy link to clipboard
Copied
I have thousands of antique pages of text and some of them have little black dots on them. These are not text layers but rather all the text is a single image layer. I am trying to remove the dots without damaging or altering the text in anyway but I don't have time to do it manually by erasing each dot with the eraser tool. I have tried every tutorial and tool I can find to solve the problem but they all seem to
1. Removes to much of the text I am trying to keep
2. Has to be selected manually one by one
3. Effects the text I am trying to preserve
Here is an example of what I am working with
Notice all the small black dots that don't belong.
Perhaps there is another tool available that I have missed but I can't seem to find one so here is what I am thinking and asking.
If I separate the black text from the white background in the layer is it possible to create a script that can select any parts of the layer/image that are a radius of say 5 pixels or less?
If I could I could then select the small dots and delete them and the main text would be preserved because off of those items will be larger than only 5 pixels.
If there is another tool I don't know about that would work I am open to that, please keep in mind I have thousands of pages so I am looking at running a script or action to accomplish this and not manually selecting all of them.
Thanks for reading this and any help that might follow,
Dan
It assumes there is only background in your file, if that has to work. Tests were done only on 648*391 pixels dimension and 72 px/in resolution document. It removes 100% black dots (not beeing 'flying' parts of letters), and doesn't affect scripture, but make it unremarkably thinner & contrastive and still remarkably similar to original Ask me question if you have any.
...function sTT(v) {return stringIDToTypeID(v)}
function cR2(v1, v2, v3, v4) {// UNSAMPLED COLOR RANGE:
eval("(dsc1 = new Action
Actually I do it for free as I'm enthusiast of Photoshop Scripting, so that was great pleasure to have another task that is other than rest I tried I could find a method for I haven't been honoured by any means yet and haven't supposed I can. Well, if you guys want to be nice for me this way - that is completetly ok if I take some little payback 2nd chance can no happen, someone is so greatful for my work that would like to emphasize this cooperation must also convey some values. I wonder what
...Copy link to clipboard
Copied
It assumes there is only background in your file, if that has to work. Tests were done only on 648*391 pixels dimension and 72 px/in resolution document. It removes 100% black dots (not beeing 'flying' parts of letters), and doesn't affect scripture, but make it unremarkably thinner & contrastive and still remarkably similar to original Ask me question if you have any.
function sTT(v) {return stringIDToTypeID(v)}
function cR2(v1, v2, v3, v4) {// UNSAMPLED COLOR RANGE:
eval("(dsc1 = new ActionDescriptor())" + (v1 == 'skinTone' ? ".putInteger(sTT('fuzziness'), v4)" : ""))
dsc1.putEnumerated(sTT(C = (c = 'color') + 's'), sTT(C), sTT(v1))
for(i = 0; i < (arr = ['invert', 'UseFacesKey']).length; i++) dsc1.putBoolean(sTT(arr), eval('v' + (i + 2)) || false)
dsc1.putInteger(sTT(c + 'Model'), 0), executeAction(sTT(c + 'Range'), dsc1, DialogModes.NO)
// v1: 'radius', 'cyans', 'yellows', 'graininess', 'blues', 'magenta'
// 'highlights', 'midtones', 'shadows', 'skinTone', 'outOfGamut'
// v2: boolean; v3: boolean, v4: number (0 - 200)
}
function pat(v) {// PATH FROM SELECTION
(ref1 = new ActionReference()).putClass(sTT('path'));
(dsc1 = new ActionDescriptor()).putReference(sTT('null'), ref1);
(ref2 = new ActionReference()).putProperty(sTT('selectionClass'), sTT('selection'))
dsc1.putReference(sTT('from'), ref2)
dsc1.putUnitDouble(sTT('tolerance'), sTT('pixelsUnit'), v)
executeAction(sTT('make'), dsc1, DialogModes.NO)
// v: (.5 - 10)
}
function SfP(v1, v2) {// SSELECTION FROM PATH:
(ref1 = new ActionReference()).putProperty(sTT('channel'), sTT('selection')); (dsc1 = new ActionDescriptor()).putReference(sTT('null'), ref1)
eval("(ref2 = new ActionReference()).put" + (!v2 ? "Enumerated" : ((wP = v2 == 'workPath') ? "Property" : (isNaN(v2) ? "Name" : "Index")))
+ "(sTT('path'), " + (!v2 ? "sTT('ordinal'), sTT('targetEnum')" : (wP ? "sTT(v2)" : "v2")) + ")")
dsc1.putReference(sTT('to'), ref2), dsc1.putInteger(sTT('version'), 1), dsc1.putBoolean(sTT('vectorMaskParams'), true)
try{executeAction(sTT(v1), dsc1, DialogModes.NO)} catch(errPth) {}
// v1: 'set', 'addTo', 'subtractFrom', 'interfaceWhite'; v2: null, 'workPath', path name
}
// LAYER FROM LAYER:
function cTL() {executeAction(sTT('copyToLayer'), undefined, DialogModes.NO)}
function pD(v) {// PATH DELETION:
eval("(ref1 = new ActionReference()).put" + (!v ? "Enumerated" : ((wP = v == 'workPath') ? "Property" : (isNaN(v) ? "Name" : "Index")))
+ "(sTT('path'), " + (!v ? "sTT('ordinal'), sTT('targetEnum')" : (wP ? "sTT(v)" : "v")) + ")");
(dsc1 = new ActionDescriptor()).putReference(sTT('null'), ref1), executeAction(sTT('delete'), dsc1, DialogModes.NO)
// v: null, 'workPath', path name / index
}
function rst() {// COLORS RESET
(ref1 = new ActionReference()).putProperty(sTT('color'), sTT('colors'));
(dsc1 = new ActionDescriptor()).putReference(sTT('null'), ref1)
executeAction(sTT('reset'), dsc1, DialogModes.NO);
}
function str(v1, v2, v3, V1, V2, V3, V4) {// THREE STATES OF STROKE:
for(i = 0; i < (arr = ['INSIDE', 'CENTER', 'OUTSIDE']).length; i++) {
eval('sel.stroke(' + V1 + ',' + V2 + ', StrokeLocation.' + arr +
', ColorBlendMode.' + V3 + ', ' + arguments + ', ' + V4 + ')' )
}
}
cR2('shadows', false, false, 27), pat(2.7), SfP('set'), pD(), (sel = (aD = activeDocument).selection).grow(23, true), sel.invert()
sel.grow(23, true), sel.invert(), cTL(), aD.activeLayer = (lyr = aD.layers)[1], sel.selectAll(), rst(), sel.fill(backgroundColor)
sel.deselect(), aD.activeLayer = lyr[0], str(25, 15, 5, 'foregroundColor', 1, 'MULTIPLY', false), aD.flatten()
Copy link to clipboard
Copied
Wow thank you, super creative and works very good. I'm going to run it against a large group and see what happens but thank you again.
Dan
Copy link to clipboard
Copied
Let me know if that works, and upload other samples to test it on, perhaps those you have problem with. If you would like to automate thousends of your images (that you won't open one after one) let me know so I put that script into other grand script automating process. You'll have for example put all of them in some folder on desktop. Then by script select it to get started...
Copy link to clipboard
Copied
I am gathering some test pages now and I also have a question but I am getting an example ready.
I would love to have an automated script and a folder on the desktop would be prefect. I'll be back in around 20 mins.
Dan
Copy link to clipboard
Copied
Ok here is a link to one of the files we actually work with Dropbox - 407_L.psd
So notice that the black text (layer 1) is actually separated from the white background (Color Fill 1) and we leave an original in the file but its hidden (Layer 0). We use 300 dpi on the files and this link is to one of the worst files we have.
Also I was wondering with the path setup you are doing is there an easy way to fill the letters that have white or holes in them?
Please let us know what we can do for you,
Dan
Copy link to clipboard
Copied
Wow, great work of me. I didn't suppose that will work also on other document (you just uploaded) that is not only one layer-background. It reduced over 80 percent of black dots. And when I flattened image before processing the result was much beter - over 90 percent reducement without affecting letters, though that sample was more advanced than 1st one.
Actually I wrote that part of filling white holes in letter too. But didn't attach to script as wasn't sure you need it. It makes letters are more complete, however some where little white spaces are part of letters may be blacked too, like in that 'M'.
Copy link to clipboard
Copied
Is there a way in the code to make the selection of the dots a little larger?
Dan
Copy link to clipboard
Copied
I'll check that later if I get what you mean, but probably bit increment of pat function value that is now set to 2.7 could help.
UPDATE:
Add this function under others they are in code:
function BaC(v1, v2, v3) {// BRIGTHNESS AND CONTRAST:
(dsc1 = new ActionDescriptor()).putInteger(sTT('brightness'), v1)
dsc1.putInteger(sTT('center'), v2), dsc1.putBoolean(sTT('useLegacy'), !v3 ? false : v3)
executeAction(sTT('brightnessEvent'), dsc1, DialogModes.NO)
}
Add this code at the bottom of script:
cR2('shadows'), BaC(25, 75), sel.contract(1), sel.smooth(1), sel.expand(1), lyr[0].applyUnSharpMask(500, 1000, 250)
sel.feather(1), sel.grow(25, true), cTL(), BaC(150, 100), aD.selection.fill(backgroundColor, ColorBlendMode.BEHIND)
lyr[0].applyGaussianBlur(.3), aD.flatten()
This additionall part is going to remove 100% little black dots and make partial reconstruction of letters. Small grey areas in letters will be turned into black while white holes will be decreased. A little changed letters (looking at with zoom) are now less frayed, however to keep their original shape (in about 95%) there couldn't be possible complete white dots elimination.
Copy link to clipboard
Copied
Thank you so much, we are going to run it against a bunch of files today. Looking forward to it.
Dan
Copy link to clipboard
Copied
Let me know all works as you want so I will find time to automate this process for many files (but it may take over an hour)
Copy link to clipboard
Copied
We are getting and error when we try to run the script.
I'm afraid i put the new code in wrong.
Can you send me the code in the order you have it?
Dan
Copy link to clipboard
Copied
Last 2 paragraphs of old code replace to these 4:
function str(v1, v2, v3, V1, V2, V3, V4) {// THREE STATES OF STROKE:
for(i = 0; i < (arr = ['INSIDE', 'CENTER', 'OUTSIDE']).length; i++) {
eval('sel.stroke(' + V1 + ',' + V2 + ', StrokeLocation.' + arr +
', ColorBlendMode.' + V3 + ', ' + arguments + ', ' + V4 + ')' )
}
}
function BaC(v1, v2, v3) {// BRIGTHNESS AND CONTRAST:
(dsc1 = new ActionDescriptor()).putInteger(sTT('brightness'), v1)
dsc1.putInteger(sTT('center'), v2), dsc1.putBoolean(sTT('useLegacy'), !v3 ? false : v3)
executeAction(sTT('brightnessEvent'), dsc1, DialogModes.NO)
}
cR2('shadows'), pat(2.7), SfP('set'), pD(), (sel = (aD = activeDocument).selection).grow(23, true), sel.invert()
sel.grow(23, true), sel.invert(), cTL(), aD.activeLayer = (lyr = aD.layers)[1], sel.selectAll(), rst(), sel.fill(backgroundColor)
sel.deselect(), aD.activeLayer = lyr[0], str(25, 15, 5, 'foregroundColor', 1, 'MULTIPLY', false), aD.flatten()
cR2('shadows'), BaC(25, 75), sel.contract(1), sel.smooth(1), sel.expand(1), lyr[0].applyUnSharpMask(500, 1000, 250)
sel.feather(1), sel.grow(25, true), cTL(), BaC(150, 100), aD.selection.fill(backgroundColor, ColorBlendMode.BEHIND)
lyr[0].applyGaussianBlur(.3), aD.flatten()
If it still doesn't work though you have correct order then maybe it doesn't work on specific layer. I tested it only on that one you lately attached. So there's chance something could go wrong but I believe you didn't combined well old code with new.
Copy link to clipboard
Copied
Sorry for the delay but I wanted to let you know that the script is working amazingly.
Let us know if we can email you small gift,
Dan
Copy link to clipboard
Copied
Actually I do it for free as I'm enthusiast of Photoshop Scripting, so that was great pleasure to have another task that is other than rest I tried I could find a method for I haven't been honoured by any means yet and haven't supposed I can. Well, if you guys want to be nice for me this way - that is completetly ok if I take some little payback 2nd chance can no happen, someone is so greatful for my work that would like to emphasize this cooperation must also convey some values. I wonder what that is, but whatever it will be I'm happy to recieve it. After all I saved weeks of your time making those texts readable.
For full automation replace last two paragraphs to this code. It's going to let you choose folder which (.psd/.jpg) files (also those nested in all level subfolders) will be processed. Then each saved as .jpg in autocreated subfolder of its parent folder:
function FSf(sF, i) {
sF = Folder(sF).getFiles()
for(re = /\.(jpe?g|psd)$/i; i < sF.length; i++) {
if (sF instanceof Folder) FSf(sF, 0)
else if(re.test(sF.name)) {
if (!((fld = Folder(sF.path + '/processed'))).exists) fld.create(); open(sF)
cR2('shadows'), pat(2.7), SfP('set'), pD(), (sel = (aD = activeDocument).selection).grow(23, true), sel.invert()
sel.grow(23, true), sel.invert(), cTL(), aD.activeLayer = (lyr = aD.layers)[1], sel.selectAll(), rst(), sel.fill(backgroundColor)
sel.deselect(), aD.activeLayer = lyr[0], str(25, 15, 5, 'foregroundColor', 1, 'MULTIPLY', false), aD.flatten()
cR2('shadows'), BaC(25, 75), sel.contract(1), sel.smooth(1), sel.expand(1), lyr[0].applyUnSharpMask(500, 1000, 250)
sel.feather(1), sel.grow(25, true), cTL(), BaC(150, 100), aD.selection.fill(backgroundColor, ColorBlendMode.BEHIND)
lyr[0].applyGaussianBlur(.3), aD.flatten(), aD.saveAs(File(fld + '/' + aD.name), jpg), aD.close()
}
}
}
displayDialogs = DialogModes.NO
jpg = new JPEGSaveOptions(); jpg.quality = 12
FSf(Folder.selectDialog('select folder'), 0)
Copy link to clipboard
Copied
Hello,
A few years back you helped create a script to remove dots from these old pages we are working with. I have been using the script off and on for years now but now that I have Adobe Photoshop 2023 I am getting an error. Please see attached image for error.
I was hoping you would be willing to update the script so I can keep using it. I have also attached a file I am using for you to test on.
Thank you again!!
Dan
Copy link to clipboard
Copied
Copy link to clipboard
Copied
Ah thank you so much!! really appreciate the help.
Copy link to clipboard
Copied
Hi, did you get this working in Photoshop 2023? I have a few documents that have alot of black dots and it is taking hours to clean them up. I would really appreciate a copy of a script like this to help me.
Thanks,
Paul