Skip to content

Instantly share code, notes, and snippets.

@Biotronic
Created July 25, 2017 21:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Biotronic/659436c820aae8f6d7e0785baaccf82e to your computer and use it in GitHub Desktop.
Save Biotronic/659436c820aae8f6d7e0785baaccf82e to your computer and use it in GitHub Desktop.
// The desired width of the image.
var desiredSize = UnitValue(prompt("Please type the desired width of your map (in pixels).", "2048"), "px");
var generatedSize = UnitValue("2048", "px");
// How many times to apply the difference clouds filter to the background.
// This can be lower for smaller maps, and probably should be bigger for ginormous ones.
var numClouds = 6;
// How many times to apply the difference clouds filter to the highlight layer.
// This should be kept low, since the goal of that layer is to highlight big landmasses (think continents).
var numBigClouds = 2;
// How many tieme to apply difference clouds to the detail layer that's used for larger maps.
var numDetailsClouds = 6
// How large the polar area should be, as a ratio to the width of the image.
var polarArea = 1/4;
// How far the polar area should blend into the background clouds, as a ratio to the width of the image.
var polarFeather = 1/32;
// How large the highlight layer should be generated, as a ratio to the width of the image.
// This should be kept small, to avoid too much detail.
var highlightArea = 1/16;
PSConstants = {
phClassAdjustmentLayer : 1097099852,
phClassAntiAliasedPICTAcquire : 1097757761,
phClassBrightnessContrast : 1114793795,
phClassChannel : 1130917484,
phClassCurves: 1131574899, // Crvs
phClassCurvesAdjustment: 1131574849, // CrvA
phClassDocument: 1147366766, // Dcmn
phClassEllipse : 1164734579,
phClassGradient: 1198679150, // Grdn
phClassGradientMap: 1197755760, // GdMp
phClassPoint: 1349415968, // Pnt
phClassPosterize: 1349743730, // Pstr
phClassProperty: 1349677170, // Prpr
phClassRGBColor: 1380401731, // RGBC
phClassThreshold : 1416131187,
phEnumBottom : 1114926957,
phEnumCustomStops: 1131639891, // CstS
phEnumFitOnScreen: 1182027630, // FtOn
phEnumLeft : 1281713780,
phEnumRGB: 1380401696, // RGB
phEnumRight : 1382508660,
phEnumTarget: 1416783732, // Trgt
phEnumTop : 1416589344,
phEnumUserStop: 1433629267, // UsrS
phEventClear: 1131177330, // Cler
phEventDelete : 1147958304,
phEventMake : 1298866208,
phEventSet : 1936028772,
phKeyAdjustment: 1097099891, // Adjs
phKeyBlue: 1114382368, // Bl
phKeyBrightness : 1114793832,
phKeyChannel: 1130917484, // Chnl
phKeyColor: 1131180576, // Clr
phKeyColorTable: 1131180628, // ClrT
phKeyColors: 1131180659, // Clrs
phKeyContrast : 1131312242,
phKeyCurve: 1131574816, // Crv
phKeyFeather : 1182034034,
phKeyGradient: 1198678372, // Grad
phKeyGradientFill: 1198679142, // Grdf
phKeyGreen: 1198681632, // Grn
phKeyHistoryStates: 1215517556, // HsSt
phKeyHorizontal: 1215461998, // Hrzn
phKeyInterpolation: 1231975538, // Intr
phKeyLevel : 1282829344,
phKeyLevels: 1282829427, // Lvls
phKeyLocation: 1281586286, // Lctn
phKeyMidpoint: 1298428014, // Mdpn
phKeyName : 1315774496,
phKeyNull: 1853189228, // null
phKeyOpacity: 1332765556, // Opct
phKeyRed: 1382293536, // Rd
phKeySelection : 1718838636,
phKeyTo: 1411391520, // T
phKeyTransparency: 1416785523, // Trns
phKeyType : 1417244773,
phKeyUsing : 1433628263,
phKeyVertical: 1450341475, // Vrtc
phKey_Source : 1411391520,
phTypeColorStopType: 1131180665, // Clry
phTypeOrdinal: 1332896878, // Ordn
phUnitPercent: 592474723, // #Prc
phUnitPixels : 592476268,
presetKind: 829,
presetKindType: 830,
presetKindDefault: 1811,
presetKindCustom: 1814,
}
//
// Selects a circle with the given coordinates. Uses antialiasing and feather as specified.
//
function selectCircle(left, top, right, bottom, antiAlias, feather){
var descriptor = new ActionDescriptor();
var selection = new ActionReference();
var area = new ActionDescriptor();
selection.putProperty( PSConstants.phClassChannel, PSConstants.phKeySelection );
descriptor.putReference( PSConstants.phKeyNull, selection );
area.putUnitDouble( PSConstants.phEnumTop, PSConstants.phUnitPixels, top );
area.putUnitDouble( PSConstants.phEnumLeft, PSConstants.phUnitPixels, left );
area.putUnitDouble( PSConstants.phEnumBottom, PSConstants.phUnitPixels, bottom );
area.putUnitDouble( PSConstants.phEnumRight, PSConstants.phUnitPixels, right );
descriptor.putObject( PSConstants.phKey_Source, PSConstants.phClassEllipse, area );
descriptor.putBoolean( PSConstants.phClassAntiAliasedPICTAcquire, antiAlias );
descriptor.putUnitDouble( PSConstants.phKeyFeather, PSConstants.phUnitPixels, feather );
executeAction( PSConstants.phEventSet, descriptor, DialogModes.NO );
}
//
// Creates a new threshold layer, then deletes its layer mask, since we don't need it here.
//
function newThresholdLayer(layerName, threshold) {
var newLayer = new ActionDescriptor();
var usingInfo = new ActionDescriptor();
var parameters = new ActionDescriptor();
var layerType = new ActionReference();
layerType.putClass( PSConstants.phClassAdjustmentLayer );
newLayer.putReference( PSConstants.phKeyNull, layerType );
parameters.putInteger( PSConstants.phKeyLevel, 128 );
usingInfo.putString( PSConstants.phKeyName, layerName );
usingInfo.putObject( PSConstants.phKeyType, PSConstants.phClassThreshold, parameters );
newLayer.putObject( PSConstants.phKeyUsing, PSConstants.phClassAdjustmentLayer, usingInfo );
executeAction( PSConstants.phEventMake, newLayer, DialogModes.NO );
deleteLayerMask();
return app.activeDocument.activeLayer;
}
//
// Deletes the layer mask of the currently selected layer.
//
function deleteLayerMask() {
var del = new ActionDescriptor();
var target = new ActionReference();
target.putEnumerated( PSConstants.phClassChannel, PSConstants.phTypeOrdinal, PSConstants.phEnumTarget );
del.putReference( PSConstants.phKeyNull, target );
executeAction( PSConstants.phEventDelete, del, DialogModes.NO );
}
function newPosterizeLayer(layerName, levels) {
var newLayer = new ActionDescriptor();
var usingInfo = new ActionDescriptor();
var parameters = new ActionDescriptor();
var layerType = new ActionReference();
layerType.putClass( PSConstants.phClassAdjustmentLayer );
newLayer.putReference( PSConstants.phKeyNull, layerType );
parameters.putInteger( PSConstants.phKeyLevels, levels );
usingInfo.putString( PSConstants.phKeyName, layerName );
usingInfo.putObject( PSConstants.phKeyType, PSConstants.phClassPosterize, parameters );
newLayer.putObject( PSConstants.phKeyUsing, PSConstants.phClassAdjustmentLayer, usingInfo );
executeAction( PSConstants.phEventMake, newLayer, DialogModes.NO );
deleteLayerMask();
return app.activeDocument.activeLayer;
}
function newGradientMap(layerName, colors, opacity) {
var gradientWidth = 4096;
opacity = (typeof opacity !== "undefined") ? opacity : [
{value : 100, location : 0},
{value : 100, location : 1}
];
var createCmd = new ActionDescriptor();
var layerType = new ActionReference();
var layer = new ActionDescriptor();
var gradientMap = new ActionDescriptor();
var gradient = new ActionDescriptor();
var colorNodes = new ActionList();
var opacityNodes = new ActionList();
layerType.putClass( PSConstants.phClassAdjustmentLayer );
createCmd.putReference( PSConstants.phKeyNull, layerType );
layer.putString( PSConstants.phKeyName, layerName );
gradient.putString( PSConstants.phKeyName, "Gradient" );
gradient.putEnumerated( PSConstants.phKeyGradientFill, PSConstants.phKeyGradientFill, PSConstants.phEnumCustomStops );
gradient.putDouble( PSConstants.phKeyInterpolation, gradientWidth );
for (i in colors) {
var color = colors[i];
var node = new ActionDescriptor();
var colorDescription = new ActionDescriptor();
colorDescription.putDouble( PSConstants.phKeyRed, color.red );
colorDescription.putDouble( PSConstants.phKeyGreen, color.green );
colorDescription.putDouble( PSConstants.phKeyBlue, color.blue );
node.putObject( PSConstants.phKeyColor, PSConstants.phClassRGBColor, colorDescription );
node.putEnumerated( PSConstants.phKeyType, PSConstants.phTypeColorStopType, PSConstants.phEnumUserStop );
node.putInteger( PSConstants.phKeyLocation, color.location * gradientWidth );
node.putInteger( PSConstants.phKeyMidpoint, 50 );
colorNodes.putObject( PSConstants.phKeyColorTable, node );
}
for (var i in opacity) {
var opNode = opacity[i];
var node = new ActionDescriptor();
node.putUnitDouble( PSConstants.phKeyOpacity, PSConstants.phUnitPercent, opNode.value );
node.putInteger( PSConstants.phKeyLocation, opNode.location * gradientWidth );
node.putInteger( PSConstants.phKeyMidpoint, 50 );
opacityNodes.putObject( PSConstants.phKeyTransparency, node );
}
gradient.putList( PSConstants.phKeyColors, colorNodes );
gradient.putList( PSConstants.phKeyTransparency, opacityNodes );
gradientMap.putObject( PSConstants.phKeyGradient, PSConstants.phClassGradient, gradient );
layer.putObject( PSConstants.phKeyType, PSConstants.phClassGradientMap, gradientMap );
createCmd.putObject( PSConstants.phKeyUsing, PSConstants.phClassAdjustmentLayer, layer );
executeAction( PSConstants.phEventMake, createCmd, DialogModes.NO );
deleteLayerMask();
return app.activeDocument.activeLayer;
}
function clearHistory() {
var clearAction = new ActionDescriptor();
var history = new ActionReference();
history.putProperty( PSConstants.phClassProperty, PSConstants.phKeyHistoryStates );
history.putEnumerated( PSConstants.phClassDocument, PSConstants.phTypeOrdinal, PSConstants.phEnumTarget );
clearAction.putReference( PSConstants.phKeyNull, history );
executeAction( PSConstants.phEventClear, clearAction, DialogModes.NO );
}
//
// Runs the adjust brightness and contrast filter in legacy mode on the currently selected layer.
//
function adjustBrightnessContrast(brightness, contrast) {
var brightnessContrast = new ActionDescriptor();
brightnessContrast.putInteger( PSConstants.phKeyBrightness, brightness );
brightnessContrast.putInteger( PSConstants.phKeyContrast, contrast );
executeAction( PSConstants.phClassBrightnessContrast, brightnessContrast, DialogModes.NO );
}
// Create new image.
// This is twice as big as required at this point, because we use polar coordinate transforms for better polar areas.
var doc = app.documents.add(generatedSize, generatedSize);
// Store old colors, so we can restore them when we're done.
var oldForegroundColor = app.foregroundColor;
var oldBackgroundColor = app.backgroundColor;
// Our clouds should be black and white.
app.foregroundColor.rgb.hexValue = "000000";
app.backgroundColor.rgb.hexValue = "FFFFFF";
// Select a circular area in the center of the image. This is where we will draw the poles.
var p1 = UnitValue(generatedSize * (0.5 - (polarArea / 2)), "px");
var p2 = UnitValue(generatedSize * (0.5 + (polarArea / 2)), "px");
selectCircle(p1, p1, p2, p2, true, generatedSize * polarFeather);
// Create the north pole by filling it with clouds.
var northPole = doc.artLayers.add();
northPole.name = "North pole";
doc.selection.fill(app.foregroundColor);
for (i = 0; i < numClouds; ++i) {
northPole.applyDifferenceClouds();
}
// Create the south pole by filling it with clouds.
var southPole = doc.artLayers.add();
southPole.name = "South pole";
doc.selection.fill(app.foregroundColor);
for (i = 0; i < numClouds; ++i) {
southPole.applyDifferenceClouds();
}
doc.selection.deselect();
// Here comes the magic - we translate our circular block in the center of the image into a line along the top.
northPole.applyPolarCoordinates(PolarConversionType.POLARTORECTANGULAR);
southPole.applyPolarCoordinates(PolarConversionType.POLARTORECTANGULAR);
// Since we end up at the top, we need to flip the south pole.
southPole.rotate(180, AnchorPosition.MIDDLECENTER);
// And since Photoshop doesn't allow us to rotate around image center, only layer center, we need to move the south pole down.
southPole.translate(0, UnitValue(generatedSize.as("px") - southPole.bounds[3].as("px"), "px"));
// We're done with the polar coordinates, so we no longer require a square image.
doc.resizeImage(generatedSize, generatedSize/2);
// Fill background with clouds.
for (i = 0; i < numClouds; ++i) {
doc.backgroundLayer.applyDifferenceClouds();
}
// And merge all layers.
northPole.merge();
southPole.merge();
// Equalize the clouds to better fill out the histogram.
doc.backgroundLayer.equalize();
if (desiredSize > generatedSize) {
var details = doc.artLayers.add();
}
// Create a layer to highlight and enhance the clouds in the background.
var highlight = doc.artLayers.add();
highlight.name = "Highlight";
// You probably want to draw something on your own in this layer, but we'll give you something to get you started.
var x1 = UnitValue(generatedSize * (0.5-highlightArea), "px");
var x2 = UnitValue(generatedSize * (0.5), "px");
// This area is square, since difference clouds seems to have problems making tilable clouds in constrained spaces.
var y1 = UnitValue(generatedSize * (0.25-highlightArea/2), "px");
var y2 = UnitValue(generatedSize * (0.25+highlightArea/2), "px");
var arr = [[x1,y1], [x1,y2], [x2,y2], [x2,y1]];
doc.selection.select(arr, SelectionType.REPLACE, 0, false);
doc.selection.fill(app.foregroundColor);
// Fill it with clouds.
for (i = 0; i < numBigClouds; ++i) {
highlight.applyDifferenceClouds();
}
doc.selection.deselect();
// Create an interesting level of interaction between this layer and the clouds.
highlight.equalize();
adjustBrightnessContrast(0, -55);
highlight.blendMode = BlendMode.HARDLIGHT;
// Since it's tilable, we should only need to resize the layer to fill the image.
// However, Photoshop blends the edges, and this doesn't work as great as I think it should.
// Instead, we duplicate this layer, move the duplicate adjacent to this one...
var h2 = highlight.duplicate();
h2.translate(UnitValue(highlightArea * generatedSize, "px"), 0);
h2.merge();
// ...and then resize twice as big as we'd otherwise need to.
highlight.resize(100/highlightArea, 100/highlightArea, AnchorPosition.MIDDLECENTER);
// Add a posterize layer to separate water from land, and to create the stepped effect you see on maps.
var posterize = newPosterizeLayer("Posterize", 13);
// Instead of a black and white heightmap, create different colors for different layers.
var gradientMap = newGradientMap("Gradient Map", [
{ red: 172, green: 213, blue: 237, location: 5/12 },
{ red: 155, green: 179, blue: 148, location: 6/12 },
{ red: 137, green: 168, blue: 128, location: 7/12 },
{ red: 159, green: 172, blue: 133, location: 8/12 },
{ red: 192, green: 194, blue: 157, location: 9/12 },
{ red: 193, green: 183, blue: 146, location: 10/12 },
{ red: 175, green: 161, blue: 125, location: 11/12 },
{ red: 143, green: 128, blue: 97, location: 12/12 }
]);
// Stop using the generated size, and scale up to desired size.
doc.resizeImage(desiredSize, desiredSize/2);
if (desiredSize > generatedSize) {
doc.activeLayer = details;
details.name = "Details";
doc.selection.selectAll();
doc.selection.fill(app.foregroundColor);
doc.selection.deselect();
for (i = 0; i < numDetailsClouds; ++i) {
details.applyDifferenceClouds();
}
details.equalize();
details.blendMode = BlendMode.OVERLAY;
details.opacity = 50;
details.merge();
}
// Restore foreground and background colors.
app.foregroundColor = oldForegroundColor;
app.backgroundColor = oldBackgroundColor;
// Select the most interesting layer for editing.
doc.activeLayer = highlight;
clearHistory();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment