Created
July 25, 2017 21:26
-
-
Save Biotronic/659436c820aae8f6d7e0785baaccf82e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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