Search code examples
photoshoptiff

How to adjust the colors of a large number of images based on one spesific?


I have a large number of photos which I want to bring to the same 'level' -same colors / brightness / contrast etc. To this end I have one initial / guide with a black & white color checker (basically the squares with colors), which I added to all other photos.

This is the initial / guide https://i.sstatic.net/Pd5Hy.jpg and these are some of the photos https://i.sstatic.net/azwGp.jpg , https://i.sstatic.net/kovxW.jpg , https://i.sstatic.net/lk9uR.jpg

As I see it the area with the little square colors (color control squares) must be the same color (hex value) in all photos in order for them to be at the same level -so I can get meaningful data from the strip below.

Is there a way to do with an automated / batch way in photoshop or some other tool?

EDIT: Note that there might be darker / lighter areas than those in the control squares which i want to preserve them (just get lighter/darker accordingly but not completely replace them with a threshold color)


Solution

  • I don't know if this is possible with any advanced tool but here's my take in Photoshop. The idea is quite simple — use a gradient map to remap target colors to source values (hence this won't work on 32bit tiffs):

    1. sample source colors from an active document (source document);
    2. ask for a path with other documents to open, start to open them one by one;
    3. sample target colors and get their position for a gradient map
    4. use source colors and target positions to create a gradient map

    Here's the result I got: left row are original documents with a piece of the source squares on top of them for a reference, right row are result documents with the gradient map applied and the same slice from the source doc on top (barely visible):

    enter image description here

    And here's the script I made.

    Note that I was using your png files so if your files are different size you might need to adjust coordinates for color samplers.

    var sampler, sampledColors, sourceCoords, targetCoords;
    
    // defining coordinates to sample 6 colors from the active source-document
    sourceCoords = [
        [55, 318],
        [190, 318],
        [310, 318],
        [420, 318],
        [560, 318],
        [690, 318],
    ];
    
    // defining coordinates to sample target colors from target documents
    targetCoords = [
        [78, 120],
        [206, 120],
        [328, 120],
        [453, 120],
        [577, 120],
        [709, 120],
    ]
    
    // a library
    var Utils = Utils ||
    {
    
        // will add photoshop Color Sampler to document
        addSample: function(coord)
        {
            return app.activeDocument.colorSamplers.add(coord);
        },
    
        // reads color from a Color Sampler
        readSample: function(sample)
        {
            return sample.color;
        },
    
        // gets a collection of Color Samplers
        getSamplers: function()
        {
            return app.activeDocument.colorSamplers;
        },
    
        // deletes a Color Sampler
        deleteSample: function(sample)
        {
            sample.remove();
        },
    
        // RGB > YUV color translation
        rgb2yuv: function(rgb)
        {
            var r = rgb[0] / 255,
                g = rgb[1] / 255,
                b = rgb[2] / 255;
    
            var y = (r * 0.299) + (g * 0.587) + (b * 0.114);
            var u = (r * -0.14713) + (g * -0.28886) + (b * 0.436);
            var v = (r * 0.615) + (g * -0.51499) + (b * -0.10001);
    
            return [y, u, v];
        },
    
        // Linear transformation
        linear: function(X, A, B, C, D, _cut)
        {
            var _cut = _cut !== undefined ? _cut : false;
            var Y = (X - A) / (B - A) * (D - C) + C
            if (_cut)
            {
                if (Y > D) Y = D;
                if (Y < C) Y = C;
            }
            return Y;
        },
    
        // changes active document color space to RGB
        docToRgb: function()
        {
            var desc16 = new ActionDescriptor();
            desc16.putClass(charIDToTypeID('T   '), charIDToTypeID('RGBM'));
            desc16.putBoolean(charIDToTypeID('Fltt'), false);
            desc16.putBoolean(charIDToTypeID('Rstr'), false);
            executeAction(charIDToTypeID('CnvM'), desc16, DialogModes.NO);
        },
    
        /**
         * @description Creates a rectangle selection in a specific coordinates with a predefined delta: -7 / +7 to 'coord' values
         * @param  {array}  - [0] is X, [1] is Y coordinates
         *
         * @return nothing
         */
        rectangleSelection: function(coord)
        {
            var delta = 7;
            var descRectangleSelection = new ActionDescriptor();
            var rectSelectionRef = new ActionReference();
            rectSelectionRef.putProperty(charIDToTypeID('Chnl'), charIDToTypeID('fsel'));
            descRectangleSelection.putReference(charIDToTypeID('null'), rectSelectionRef);
            var descCoords = new ActionDescriptor();
            descCoords.putUnitDouble(charIDToTypeID('Top '), charIDToTypeID('#Pxl'), coord[1] - delta);
            descCoords.putUnitDouble(charIDToTypeID('Left'), charIDToTypeID('#Pxl'), coord[0] - delta);
            descCoords.putUnitDouble(charIDToTypeID('Btom'), charIDToTypeID('#Pxl'), coord[1] + delta);
            descCoords.putUnitDouble(charIDToTypeID('Rght'), charIDToTypeID('#Pxl'), coord[0] + delta);
            descRectangleSelection.putObject(charIDToTypeID('T   '), charIDToTypeID('Rctn'), descCoords);
            executeAction(charIDToTypeID('setd'), descRectangleSelection, DialogModes.NO);
        },
    
        /**
         * @description saves an active document as a TIF file
         * @param  {object} data - .name (without extension) for a name and data.path for a path
         *
         * @return nothing
         */
        saveTIF: function(data)
        {
            if (!new Folder(data.path).exists) new Folder(data.path).create();
            var desc = new ActionDescriptor();
            var descOptions = new ActionDescriptor();
            descOptions.putEnumerated(charIDToTypeID('BytO'), charIDToTypeID('Pltf'), charIDToTypeID('Mcnt'));
            descOptions.putEnumerated(stringIDToTypeID('layerCompression'), charIDToTypeID('Encd'), stringIDToTypeID('RLE'));
            desc.putObject(charIDToTypeID('As  '), charIDToTypeID('TIFF'), descOptions);
            desc.putPath(charIDToTypeID('In  '), new File(data.path + "/" + data.name + ".tif"));
            executeAction(charIDToTypeID('save'), desc, DialogModes.NO);
        },
    };
    
    // this will get colors from the source document
    var getSamplersData = function(coordinates)
    {
        var colors = [];
        var color, sampler;
    
        // makes sure the doc is in rgb
        Utils.docToRgb();
    
        // for all coordinates..
        for (var i = 0; i < coordinates.length; i++)
        {
            // create a rectangular selection of 14x14 pixels in the coordinate
            Utils.rectangleSelection(coordinates[i]);
    
            // average blur it to make sure color sampler samples an average color from noisy square because there's no option for color sample size for Color Samplers
            activeDocument.activeLayer.applyAverage();
            activeDocument.selection.deselect();
    
            // ads a color sample
            sampler = Utils.addSample(coordinates[i]);
    
            // reads a color sample
            color = Utils.readSample(sampler);
    
            // color is added to [colors]
            colors.push(color);
            Utils.deleteSample(sampler);
        }
        return colors;
    };
    
    // creates gradient maps for new documents
    var setSamplerData = function()
    {
        var workFolder;
    
        var controller = function(originalColors)
        {
            var docs, doc, docSampler, sampledColors, gradientColors;
    
            try
            {
                docs = getDocs(); // asks for a folder to work with
            }
            catch (e)
            {
                return false;
            }
    
            // for all found documents...
            for (var i = 0; i < docs.length; i++)
            {
                try
                {
                    // opening it and makes sure it's in rgb mode
                    doc = openDocument(docs[i]);
                }
                catch (e)
                {
                    return false;
                }
    
                // getting current colors in the color boxes
                sampledColors = getSamplersData(targetCoords);
    
                // create an array of color for a gradient map using current colors positions and original colors
                gradientColors = createGradientDataFromColors(originalColors, sampledColors);
    
                // creates a gradient map
                createGradient(gradientColors);
    
                // saves a file
                Utils.saveTIF(
                {
                    path: workFolder + "/export",
                    name: activeDocument.name
                });
            }
        };
    
        /////////////////////////////////////////////////////////////////////////////////////
        // this will as for a folder and will return found docs
        var getDocs = function()
        {
            var docs;
    
            workFolder = Folder.selectDialog();
            if (workFolder == null) throw 'cancelled';
    
            docs = workFolder.getFiles('*');
    
            for (var i = docs.length - 1; i >= 0; i--)
            {
    
                if (docs[i] instanceof Folder) docs.splice(i, 1);
            }
    
            if (docs.length == 0) throw 'no files in the folder';
    
            return docs;
        }; // end of getDocs()
    
        // opens a doc and makes sure it's in rgb color mode
        var openDocument = function(path)
        {
            var doc;
            try
            {
                doc = app.open(new File(path));
                Utils.docToRgb();
                return doc;
            }
            catch (e)
            {
                alert("can't open " + path + "\nAborting");
                throw e;
            }
        };
    
        // this will create a gradient map 
        var createGradientDataFromColors = function(original, sampled)
        {
            var colors = [];
            var rgbOriginal, rgbSampled, positionSampled;
    
            for (var i = 0; i < original.length; i++)
            {
                rgbOriginal = getRGB(original[i]); // get an array of [r,g,b] from SolidColor object
                rgbSampled = getRGB(sampled[i]); // get an array of [r,g,b] from SolidColor object
                positionSampled = Math.round(Utils.rgb2yuv(rgbSampled)[0] * 10000) / 100; // getting positions from the current document colors
    
                colors.push(
                {
                    color: rgbOriginal,
                    pos: positionSampled
                });
            }
    
            return colors;
        }; // end of createGradientDataFromColors()
    
        // this will convert an rgb from Solid Color to an array of [r, g and b]
        var getRGB = function(color)
        {
            return [color.rgb.red, color.rgb.green, color.rgb.blue];
        }; // end of getRGB()
    
        // creates a gradient map
        // colors are from the original doc, positions are from the target docs
        var createGradient = function(data)
        {
            var descGradMap = new ActionDescriptor();
            var referenceMap = new ActionReference();
            referenceMap.putClass(charIDToTypeID('AdjL'));
            descGradMap.putReference(charIDToTypeID('null'), referenceMap);
            var desc5 = new ActionDescriptor();
            var desc6 = new ActionDescriptor();
            var desc7 = new ActionDescriptor();
    
            desc7.putEnumerated(charIDToTypeID('GrdF'), charIDToTypeID('GrdF'), charIDToTypeID('CstS'));
            desc7.putDouble(charIDToTypeID('Intr'), 4096.000000);
    
            var list1 = new ActionList();
            var el;
    
            for (var i = 0; i < data.length; i++)
            {
                el = data[i];
    
                var descTemp = new ActionDescriptor();
                var descColor = new ActionDescriptor();
                descColor.putDouble(charIDToTypeID('Rd  '), el.color[0]);
                descColor.putDouble(charIDToTypeID('Grn '), el.color[1]);
                descColor.putDouble(charIDToTypeID('Bl  '), el.color[2]);
                descTemp.putObject(charIDToTypeID('Clr '), charIDToTypeID('RGBC'), descColor);
                descTemp.putEnumerated(charIDToTypeID('Type'), charIDToTypeID('Clry'), charIDToTypeID('UsrS'));
                descTemp.putInteger(charIDToTypeID('Lctn'), Utils.linear(el.pos, 0, 100, 0, 4096));
                descTemp.putInteger(charIDToTypeID('Mdpn'), 50);
                list1.putObject(charIDToTypeID('Clrt'), descTemp);
            }
    
            desc7.putList(charIDToTypeID('Clrs'), list1);
    
            var list2 = new ActionList();
            var desc12 = new ActionDescriptor();
            desc12.putUnitDouble(charIDToTypeID('Opct'), charIDToTypeID('#Prc'), 100.000000);
            desc12.putInteger(charIDToTypeID('Lctn'), 0);
            desc12.putInteger(charIDToTypeID('Mdpn'), 50);
            list2.putObject(charIDToTypeID('TrnS'), desc12);
            var desc13 = new ActionDescriptor();
            desc13.putUnitDouble(charIDToTypeID('Opct'), charIDToTypeID('#Prc'), 100.000000);
            desc13.putInteger(charIDToTypeID('Lctn'), 4096);
            desc13.putInteger(charIDToTypeID('Mdpn'), 50);
            list2.putObject(charIDToTypeID('TrnS'), desc13);
            desc7.putList(charIDToTypeID('Trns'), list2);
    
            desc6.putObject(charIDToTypeID('Grad'), charIDToTypeID('Grdn'), desc7);
            desc5.putObject(charIDToTypeID('Type'), charIDToTypeID('GdMp'), desc6);
    
            descGradMap.putObject(charIDToTypeID('Usng'), charIDToTypeID('AdjL'), desc5);
            executeAction(charIDToTypeID('Mk  '), descGradMap, DialogModes.NO);
        };
    
        return controller;
    };
    
    sampledColors = getSamplersData(sourceCoords);
    
    sampler = setSamplerData();
    sampler(sampledColors);