Skip to content

Instantly share code, notes, and snippets.

@Actine
Created August 16, 2022 22:35
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Actine/3b90fb5b8de8a729690b3c81b48119fc to your computer and use it in GitHub Desktop.
Save Actine/3b90fb5b8de8a729690b3c81b48119fc to your computer and use it in GitHub Desktop.
Edit Images Pack (public part)
import * as coda from "@codahq/packs-sdk";
export const pack = coda.newPack();
// ------------------------------------------------------------------------------------------------------------------------------
// #region Common
// ------------------------------------------------------------------------------------------------------------------------------
const HOSTED_IMAGE_URL_REGEX = /^https:\/\/(?:(?:[^/]*\.)?codahosted\.io\/|codaio\.imgix\.net\/|ct\.imgix\.net\/|assets\.imgix\.net\/~text)/;
const ERROR_NOT_IMAGE_COLUMN = "This pack can only process images uploaded to Coda through an Image column.";
const IMAGE_PARAM = coda.makeParameter({
type: coda.ParameterType.Image,
name: "image",
description: "Source image, uploaded to Coda or returned by another Edit Images pack formula. Not compatible with external images or Image URL columns."
});
const utils = {
cleanUpImageUrl : function(input : string) : string {
return input.replace('codahosted.io', 'codaio.imgix.net').replace('auto=format%2Ccompress&fit=max', '');
},
makeAdjustmentDescription : function (description : string, min : number, max : number, defValue : number) : string {
return `${description} Valid values are in the range from ${min < 0 ? '−' + (min * -1) : min} to ${max}. The default value is ${defValue}, which leaves the image unchanged.`
},
validateParameterRange : function (value : number, min: number, max : number) : number {
if (value >= min && value <= max) {
return value
} else {
throw new coda.UserVisibleError(`Parameter value ${value} is outside of valid range [${min}, ${max}]`);
}
},
validateAndApplyCommonTextParams : function (font, fontSize, color, outlineWidth, outlineColor, shadowStrength, horizontalPosition, verticalPosition, padding, ligatures) {
if (font !== undefined && !FONTS.map(f => f.p).includes(font) && ((<string>font).split(',').filter(x => !FONT_AUTOCOMPLETE_OPTIONS.includes(x)).length !== 0)) {
throw new coda.UserVisibleError("Invalid value specified as font. Use one of the valid CSS names or one of the supported fonts (the list is available as the Fonts sync table.)");
}
if (fontSize !== undefined && fontSize <= 0) {
throw new coda.UserVisibleError("Invalid fontSize. Valid values are larger than 0.");
}
if (color !== undefined && !color.match(/#?([0-9a-fA-F]?[0-9a-fA-F]{3}){1,2}/) && !COLOR_NAMES.includes(color.toLowerCase())) {
throw new coda.UserVisibleError("Invalid text color. Use RGB, ARGB, RRGGBB, AARRGGBB, or one of the colors from the list of named colors: https://drafts.csswg.org/css-color/#named-colors");
}
if (outlineWidth !== undefined && outlineWidth <= 0) {
throw new coda.UserVisibleError("Invalid outline width. Valid values are larger than 0.");
}
if (outlineColor !== undefined && outlineWidth > 0 && !outlineColor.match(/#?([0-9a-fA-F]?[0-9a-fA-F]{3}){1,2}/) && !COLOR_NAMES.includes(outlineColor.toLowerCase())) {
throw new coda.UserVisibleError("Invalid outlineColor. Use RGB, ARGB, RRGGBB, AARRGGBB, or one of the colors from the list of named colors: https://drafts.csswg.org/css-color/#named-colors");
}
// Shadow is validated in arg assignment
if (padding !== undefined && padding < 0) {
throw new coda.UserVisibleError("Invalid padding. Valid values are equal or larger than 0.");
}
if (ligatures !== undefined && !TEXT_LIGATURE_MODES.includes(ligatures)) {
throw new coda.UserVisibleError("Invalid ligatures setting. Valid values are None, Common, All.");
}
let x : number, y : number, halign : string, valign : string, align = [];
if (horizontalPosition !== undefined) {
x = parseInt(horizontalPosition);
if (isNaN(x)) {
x = undefined;
halign = horizontalPosition.toLowerCase();
}
}
if (verticalPosition !== undefined) {
y = parseInt(verticalPosition);
if (isNaN(y)) {
y = undefined;
valign = verticalPosition.toLowerCase();
}
}
let args = {};
if (font !== undefined) { args['txt-font'] = font; }
if (fontSize !== undefined) { args['txt-size'] = fontSize; }
if (color !== undefined) { args['txt-color'] = color.replace('#', ''); }
if (outlineWidth !== undefined) { args['txt-line'] = outlineWidth; }
if (outlineColor !== undefined) { args['txt-line-color'] = outlineColor.replace('#', ''); }
if (shadowStrength !== undefined) { args['txt-shad'] = utils.validateParameterRange(shadowStrength, 0, 10); }
if (padding !== undefined) { args['txt-pad'] = padding; }
if (ligatures !== undefined) { args['txt-lig'] = TEXT_LIGATURE_MODES.indexOf(ligatures); }
if (x !== undefined) { args['txt-x'] = x; } else { align.push(halign || "left"); }
if (y !== undefined) { args['txt-y'] = y; } else { align.push(valign || "top"); }
if (align.length !== 0) { args['txt-align'] = align.join(','); }
return args;
}
}
// ------------------------------------------------------------------------------------------------------------------------------
// #endregion
// #region 1. Adjustment
// ------------------------------------------------------------------------------------------------------------------------------
/**
* Adjust - brightness, contrast etc
*/
pack.addFormula({
name: "Adjust",
description: "Adjust brighness and colors similarly to how you do it in image-editing apps.",
parameters: [
IMAGE_PARAM,
coda.makeParameter({
type: coda.ParameterType.Number,
name: "brightness",
description: utils.makeAdjustmentDescription("Adjusts the overall brightness of the image.", -100, 100, 0),
optional: true
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "contrast",
description: utils.makeAdjustmentDescription("Adjusts the contrast of the image.", -100, 100, 0),
optional: true
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "saturation",
description: utils.makeAdjustmentDescription("Adjusts the saturation of the image.", -100, 100, 0) + " A value of -100 will convert the image to grayscale.",
optional: true
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "vibrance",
description: utils.makeAdjustmentDescription("Adjusts the color saturation of the image while keeping pleasing skin tones.", -100, 100, 0),
optional: true
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "hueShift",
description: utils.makeAdjustmentDescription("Changes the hue, or tint, of each pixel in the image. The value is an angle along a hue color wheel, with the pixel's starting color at 0.", -360, 360, 0),
optional: true
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "exposure",
description: utils.makeAdjustmentDescription("Adjusts the exposure setting for an image, similar to changing the F-stop on a camera.", -100, 100, 0),
optional: true
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "gamma",
description: utils.makeAdjustmentDescription("Adjusts gamma and midtone brightness.", -100, 100, 0),
optional: true
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "highlights",
description: utils.makeAdjustmentDescription("Adjusts the highlight tonal mapping of an image while preserving detail in highlighted areas.", -100, 0, 0),
optional: true
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "shadows",
description: utils.makeAdjustmentDescription("Adjusts the shadow tonal mapping of an image while preserving detail in shadowed areas.", -100, 0, 0),
optional: true
})
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([imageUrl, brightness, contrast, saturation, vibrance, hueShift, exposure, gamma, highlights, shadows]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
let args = {};
if (brightness !== undefined) { args['bri'] = utils.validateParameterRange(brightness, -100, 100); }
if (contrast !== undefined) { args['con'] = utils.validateParameterRange(contrast, -100, 100); }
if (saturation !== undefined) { args['sat'] = utils.validateParameterRange(saturation, -100, 100); }
if (vibrance !== undefined) { args['vib'] = utils.validateParameterRange(vibrance, -100, 100); }
if (hueShift !== undefined) { args['hue'] = (utils.validateParameterRange(hueShift, -360, 360) + 360) % 360; }
if (exposure !== undefined) { args['exp'] = utils.validateParameterRange(exposure, -100, 100); }
if (gamma !== undefined) { args['gam'] = utils.validateParameterRange(gamma, -100, 100); }
if (highlights !== undefined) { args['high'] = utils.validateParameterRange(highlights, -100, 0); }
if (shadows !== undefined) { args['shad'] = utils.validateParameterRange(shadows, -100, 0); }
return coda.withQueryParams(utils.cleanUpImageUrl(imageUrl), args);
},
});
/**
* Sharpen - with a simple luma sharpness
*/
pack.addFormula({
name: "Sharpen",
description: "Sharpen the image using luminance (which only affects the black and white values), providing crisp detail with minimal color artifacts.",
parameters: [
IMAGE_PARAM,
coda.makeParameter({
type: coda.ParameterType.Number,
name: "amount",
description: utils.makeAdjustmentDescription("Strength of the Sharpen effect.", 0, 100, 0)
})
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([imageUrl, amount]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
return coda.withQueryParams(utils.cleanUpImageUrl(imageUrl), {
'sharp' : utils.validateParameterRange(amount, 0, 100)
});
},
});
/**
* Unsharp Mask - with a simple luma sharpness
*/
pack.addFormula({
name: "UnsharpMask",
description: "Sharpen the image details using an unsharp mask (a blurred, inverted copy of the image.) Use this for thumbnails and fine-tuned sharpening. For images with general noise consider using simple Sharpen instead.",
parameters: [
IMAGE_PARAM,
coda.makeParameter({
type: coda.ParameterType.Number,
name: "amount",
description: utils.makeAdjustmentDescription("Presence of the Unsharp mask in the color mix.", -100, 100, 0)
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "radius",
description: "Unsharp mask blur radius. Valid values are positive numbers. The default is 2.5 px.",
suggestedValue: 2.5
})
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([imageUrl, amount, radius]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
if (amount === 0) {
return imageUrl;
}
if (radius < 0) {
throw new coda.UserVisibleError(`Unsharp mask radius must be a positive value.`);
}
return coda.withQueryParams(utils.cleanUpImageUrl(imageUrl), {
'usm' : utils.validateParameterRange(amount, -100, 100),
'usmrad' : radius
});
},
});
/**
* Denoise - reduce noise with threshold
*/
pack.addFormula({
name: "Denoise",
description: "Minimize noise in the image and clean up small artifacts.",
parameters: [
IMAGE_PARAM,
coda.makeParameter({
type: coda.ParameterType.Number,
name: "bound",
description: "Sets the threshold at which noise is removed. Pixels with a luminance value that falls below the threshold are considered noise and will be blurred. Pixels above the threshold will be sharpened according to the radius parameter value. Valid values are in the range from −100 to 100. The default value is 20.",
suggestedValue: 20
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "amount",
description: "Determines the amount of sharpening in pixels above the bound, reducing noise in the image. Valid values are in the range from −100 to 100. The default value is 20.",
suggestedValue: 20
})
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([imageUrl, bound, amount]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
if (bound === 0 && amount === 0) {
return imageUrl;
}
return coda.withQueryParams(utils.cleanUpImageUrl(imageUrl), {
'nr' : utils.validateParameterRange(bound, -100, 100),
'nrs' : utils.validateParameterRange(amount, -100, 100)
});
},
});
/**
* Enhance - auto
*/
pack.addFormula({
name: "Enhance",
description: "Automatically enhance the image adjusting the distribution of highlights, midtones, and shadows. Overall, the enhancement gives images a more vibrant appearance and works best with editorial photography, stock photography, and user-generated content for social media applications.",
parameters: [
IMAGE_PARAM
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([imageUrl]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
// Find any "auto" params that are already used
let autoValue = imageUrl.match(/(?<=[?&]auto=)[^&$]+/);
let arg = {};
if (!!autoValue) {
arg['auto'] = autoValue[0] + ",enhance"
} else {
arg['auto'] = "enhance"
}
return coda.withQueryParams(utils.cleanUpImageUrl(imageUrl), arg);
},
});
/**
* RemoveRedEyes - auto
*/
pack.addFormula({
name: "RemoveRedEyes",
description: "Automatically apply red eye removal to any detected faces.",
parameters: [
IMAGE_PARAM
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([imageUrl]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
// Find any "auto" params that are already used
let autoValue = imageUrl.match(/(?<=[?&]auto=)[^&$]+/);
let arg = {};
if (!!autoValue) {
arg['auto'] = autoValue[0] + ",redeye"
} else {
arg['auto'] = "redeye"
}
return coda.withQueryParams(utils.cleanUpImageUrl(imageUrl), arg);
},
});
// ------------------------------------------------------------------------------------------------------------------------------
// #endregion
// #region 2. Stylization
// ------------------------------------------------------------------------------------------------------------------------------
const CSS_COLORS = [
{"n":"palevioletred","c":"#db7093"},{"n":"pink","c":"#ffc0cb"},{"n":"lightpink","c":"#ffb6c1"},{"n":"snow","c":"#fffafa"},
{"n":"rosybrown","c":"#bc8f8f"},{"n":"lightcoral","c":"#f08080"},{"n":"crimson","c":"#dc143c"},{"n":"indianred","c":"#cd5c5c"},
{"n":"mistyrose","c":"#ffe4e1"},{"n":"brown","c":"#a52a2a"},{"n":"salmon","c":"#fa8072"},{"n":"firebrick","c":"#b22222"},
{"n":"tomato","c":"#ff6347"},{"n":"maroon","c":"#800000"},{"n":"darkred","c":"#8b0000"},{"n":"red","c":"#ff0000"},
{"n":"darksalmon","c":"#e9967a"},{"n":"orangered","c":"#ff4500"},{"n":"coral","c":"#ff7f50"},{"n":"lightsalmon","c":"#ffa07a"},
{"n":"sienna","c":"#a0522d"},{"n":"chocolate","c":"#d2691e"},{"n":"saddlebrown","c":"#8b4513"},{"n":"seashell","c":"#fff5ee"},
{"n":"sandybrown","c":"#f4a460"},{"n":"darkorange","c":"#ff8c00"},{"n":"peru","c":"#cd853f"},{"n":"peachpuff","c":"#ffdab9"},
{"n":"linen","c":"#faf0e6"},{"n":"orange","c":"#ffa500"},{"n":"bisque","c":"#ffe4c4"},{"n":"burlywood","c":"#deb887"},
{"n":"tan","c":"#d2b48c"},{"n":"antiquewhite","c":"#faebd7"},{"n":"navajowhite","c":"#ffdead"},{"n":"blanchedalmond","c":"#ffebcd"},
{"n":"darkgoldenrod","c":"#b8860b"},{"n":"goldenrod","c":"#daa520"},{"n":"papayawhip","c":"#ffefd5"},{"n":"moccasin","c":"#ffe4b5"},
{"n":"wheat","c":"#f5deb3"},{"n":"oldlace","c":"#fdf5e6"},{"n":"floralwhite","c":"#fffaf0"},{"n":"gold","c":"#ffd700"},
{"n":"cornsilk","c":"#fff8dc"},{"n":"khaki","c":"#f0e68c"},{"n":"darkkhaki","c":"#bdb76b"},{"n":"palegoldenrod","c":"#eee8aa"},
{"n":"lemonchiffon","c":"#fffacd"},{"n":"olive","c":"#808000"},{"n":"yellow","c":"#ffff00"},{"n":"lightgoldenrodyellow","c":"#fafad2"},
{"n":"lightyellow","c":"#ffffe0"},{"n":"beige","c":"#f5f5dc"},{"n":"ivory","c":"#fffff0"},{"n":"olivedrab","c":"#6b8e23"},
{"n":"yellowgreen","c":"#9acd32"},{"n":"darkolivegreen","c":"#556b2f"},{"n":"greenyellow","c":"#adff2f"},{"n":"chartreuse","c":"#7fff00"},
{"n":"lawngreen","c":"#7cfc00"},{"n":"darkgreen","c":"#006400"},{"n":"green","c":"#008000"},{"n":"lime","c":"#00ff00"},
{"n":"limegreen","c":"#32cd32"},{"n":"forestgreen","c":"#228b22"},{"n":"palegreen","c":"#98fb98"},{"n":"lightgreen","c":"#90ee90"},
{"n":"darkseagreen","c":"#8fbc8f"},{"n":"honeydew","c":"#f0fff0"},{"n":"springgreen","c":"#00ff7f"},{"n":"seagreen","c":"#2e8b57"},
{"n":"mediumseagreen","c":"#3cb371"},{"n":"mediumspringgreen","c":"#00fa9a"},{"n":"mintcream","c":"#f5fffa"},
{"n":"mediumaquamarine","c":"#66cdaa"},{"n":"aquamarine","c":"#7fffd4"},{"n":"turquoise","c":"#40e0d0"},
{"n":"lightseagreen","c":"#20b2aa"},{"n":"mediumturquoise","c":"#48d1cc"},{"n":"darkcyan","c":"#008b8b"},
{"n":"aqua","c":"#00ffff"},{"n":"cyan","c":"#00ffff"},{"n":"teal","c":"#008080"},{"n":"darkslategray","c":"#2f4f4f"},
{"n":"darkslategrey","c":"#2f4f4f"},{"n":"paleturquoise","c":"#afeeee"},{"n":"darkturquoise","c":"#00ced1"},
{"n":"lightcyan","c":"#e0ffff"},{"n":"azure","c":"#f0ffff"},{"n":"cadetblue","c":"#5f9ea0"},{"n":"powderblue","c":"#b0e0e6"},
{"n":"lightblue","c":"#add8e6"},{"n":"skyblue","c":"#87ceeb"},{"n":"deepskyblue","c":"#00bfff"},{"n":"lightskyblue","c":"#87cefa"},
{"n":"aliceblue","c":"#f0f8ff"},{"n":"slategray","c":"#708090"},{"n":"slategrey","c":"#708090"},
{"n":"lightslategray","c":"#778899"},{"n":"lightslategrey","c":"#778899"},{"n":"steelblue","c":"#4682b4"},
{"n":"lightsteelblue","c":"#b0c4de"},{"n":"dodgerblue","c":"#1e90ff"},{"n":"cornflowerblue","c":"#6495ed"},
{"n":"royalblue","c":"#4169e1"},{"n":"ghostwhite","c":"#f8f8ff"},{"n":"lavender","c":"#e6e6fa"},
{"n":"darkslateblue","c":"#483d8b"},{"n":"slateblue","c":"#6a5acd"},{"n":"mediumslateblue","c":"#7b68ee"},
{"n":"midnightblue","c":"#191970"},{"n":"blue","c":"#0000ff"},{"n":"darkblue","c":"#00008b"},
{"n":"mediumblue","c":"#0000cd"},{"n":"navy","c":"#000080"},{"n":"mediumpurple","c":"#9370db"},
{"n":"rebeccapurple","c":"#663399"},{"n":"blueviolet","c":"#8a2be2"},{"n":"indigo","c":"#4b0082"},
{"n":"darkviolet","c":"#9400d3"},{"n":"darkorchid","c":"#9932cc"},{"n":"mediumorchid","c":"#ba55d3"},
{"n":"thistle","c":"#d8bfd8"},{"n":"plum","c":"#dda0dd"},{"n":"violet","c":"#ee82ee"},
{"n":"orchid","c":"#da70d6"},{"n":"fuchsia","c":"#ff00ff"},{"n":"magenta","c":"#ff00ff"},
{"n":"darkmagenta","c":"#8b008b"},{"n":"purple","c":"#800080"},{"n":"mediumvioletred","c":"#c71585"},
{"n":"hotpink","c":"#ff69b4"},{"n":"lavenderblush","c":"#fff0f5"},{"n":"deeppink","c":"#ff1493"},
{"n":"black","c":"#000000"},{"n":"darkgray","c":"#a9a9a9"},{"n":"darkgrey","c":"#a9a9a9"},
{"n":"dimgray","c":"#696969"},{"n":"dimgrey","c":"#696969"},{"n":"gainsboro","c":"#dcdcdc"},
{"n":"gray","c":"#808080"},{"n":"grey","c":"#808080"},{"n":"lightgray","c":"#d3d3d3"},
{"n":"lightgrey","c":"#d3d3d3"},{"n":"silver","c":"#c0c0c0"},{"n":"white","c":"#ffffff"},
{"n":"whitesmoke","c":"#f5f5f5"}
];
const COLOR_NAMES = CSS_COLORS.map(i => i.n);
const PALLETTES = [ "CSS Named Colors" ];
/**
* Colors sync table
*/
pack.addSyncTable({
name: "Colors",
description: "A selection of named colors that can be used with the Edit Images pack",
identityName: "Color",
schema: coda.makeObjectSchema({
properties: {
name: { type: coda.ValueType.String },
colorHexValue: { type: coda.ValueType.String },
specimen: { type: coda.ValueType.String, codaType: coda.ValueHintType.ImageReference }
},
displayProperty: "name",
idProperty: "name",
featuredProperties: [ 'name', 'colorHexValue', 'specimen' ]
}),
formula: {
name: "SyncColors",
description: "Sync the list of CSS colors",
parameters: [
coda.makeParameter({
type: coda.ParameterType.String,
name: "palette",
description: "A collection of colors to sync. At the moment there’s only one palette available, CSS Named Colors.",
suggestedValue: PALLETTES[0],
autocomplete: PALLETTES
}),
],
execute: async function ([palette]) {
if (!PALLETTES.includes(palette)) {
throw new coda.UserVisibleError(`Unknown palette ${palette}. Please select one of the available palettes.`)
}
// todo: refactor this hard-coded value
let result = [];
for (let color of CSS_COLORS) {
result.push({
name: color.n,
colorHexValue: color.c,
specimen: coda.withQueryParams("https://assets.imgix.net/~text", {
'txt': '',
'w' : 8,
'h': 8,
'bg' : color.c.replace('#', '')
})
});
}
return {result};
}
}
});
/**
* Invert
*/
pack.addFormula({
name: "Invert",
description: "Invert all pixel colors and brightness values within the image, producing a negative of the image.",
parameters: [
IMAGE_PARAM
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([imageUrl]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
return coda.withQueryParams(utils.cleanUpImageUrl(imageUrl), {
'invert' : true
});
},
});
/**
* Grayscale
*/
pack.addFormula({
name: "Grayscale",
description: "Convert all pixels to the corresponding shades of gray. A shorthand for image.Adjust(saturation: -100)",
parameters: [
IMAGE_PARAM
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([imageUrl]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
return coda.withQueryParams(utils.cleanUpImageUrl(imageUrl), {
'sat' : -100
});
},
});
/**
* Monochrome - recolor with one color & alpha (intensity)
*/
pack.addFormula({
name: "Monochrome",
description: "Apply an overall monochromatic hue change: keep each pixel’s brightness but recolor them to a selected hue.",
parameters: [
IMAGE_PARAM,
coda.makeParameter({
type: coda.ParameterType.String,
name: "color",
description: "A color keyword or a 3- (RGB), 4- (ARGB), 6- (RRGGBB), or 8-digit (AARRGGBB) hexadecimal value. The “A” represents the color’s alpha transparency and therefore the intensity of the monochromatic transformation.",
autocomplete: COLOR_NAMES
})
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([imageUrl, color]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
if (!color.match(/#?([0-9a-fA-F]?[0-9a-fA-F]{3}){1,2}/) && !COLOR_NAMES.includes(color.toLowerCase())) {
throw new coda.UserVisibleError("Invalid color. Use RGB, ARGB, RRGGBB, AARRGGBB, or one of the colors from the list of named colors: https://drafts.csswg.org/css-color/#named-colors");
}
return coda.withQueryParams(utils.cleanUpImageUrl(imageUrl), {
'monochrome' : color.replace('#', '')
});
},
});
/**
* Sepia
*/
pack.addFormula({
name: "Sepia",
description: "Apply a sepia-toning effect to the image.",
parameters: [
IMAGE_PARAM,
coda.makeParameter({
type: coda.ParameterType.Number,
name: "intensity",
description: utils.makeAdjustmentDescription("Effect intensity.", 0, 100, 0),
suggestedValue: 100
})
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([imageUrl, intensity]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
if (intensity == 0) { return imageUrl; }
return coda.withQueryParams(utils.cleanUpImageUrl(imageUrl), {
'sepia' : utils.validateParameterRange(intensity, 0, 2000)
});
},
});
/**
* Duotone - recolor with two colors (for black and white) & alpha (intensity)
*/
pack.addFormula({
name: "Duotone",
description: "Apply a duotone effect — a gradient with two different colors as its endpoints — to the image. The image is converted to grayscale, and selected contrasting colors are then mapped to that gradient.",
parameters: [
IMAGE_PARAM,
coda.makeParameter({
type: coda.ParameterType.String,
name: "lighterColor",
description: "A color keyword or a 3- (RGB) or 6- (RRGGBB) hexadecimal value for the light end of the gradient.",
autocomplete: COLOR_NAMES
}),
coda.makeParameter({
type: coda.ParameterType.String,
name: "darkerColor",
description: "A color keyword or a 3- (RGB) or 6- (RRGGBB) hexadecimal value for the dark end of the gradient.",
autocomplete: COLOR_NAMES
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "opacity",
description: "The opacity of the duotone effect in range from 0 to 100. The value of 0 means the image is unchanged. The default value of 100 means the image is completely duotone.",
optional: true,
suggestedValue: 100
}),
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([imageUrl, lighterColor, darkerColor, opacity]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
if (opacity === 0) { return imageUrl; }
if (!lighterColor.match(/#?([0-9a-fA-F]{3}){1,2}/) && !COLOR_NAMES.includes(lighterColor.toLowerCase())) {
throw new coda.UserVisibleError("Invalid lighter color. Use RGB, RRGGBB, or one of the colors from the list of named colors: https://drafts.csswg.org/css-color/#named-colors");
}
if (!darkerColor.match(/#?([0-9a-fA-F]{3}){1,2}/) && !COLOR_NAMES.includes(darkerColor.toLowerCase())) {
throw new coda.UserVisibleError("Invalid darker color. Use RGB, RRGGBB, or one of the colors from the list of named colors: https://drafts.csswg.org/css-color/#named-colors");
}
return coda.withQueryParams(utils.cleanUpImageUrl(imageUrl), {
'duotone' : darkerColor.replace('#', '') + ',' + lighterColor.replace('#', ''),
'duotone-alpha' : !!opacity ? utils.validateParameterRange(opacity, 0, 100) : 100
});
},
});
/**
* Halftone
*/
pack.addFormula({
name: "Halftone",
description: "Apply a half-toning effect to the image.",
parameters: [
IMAGE_PARAM,
coda.makeParameter({
type: coda.ParameterType.Number,
name: "size",
description: utils.makeAdjustmentDescription("The width of halftone dots.", 0, 100, 0)
})
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([imageUrl, size]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
if (size == 0) { return imageUrl; }
return coda.withQueryParams(utils.cleanUpImageUrl(imageUrl), {
'htn' : utils.validateParameterRange(size, 0, 2000)
});
},
});
/**
* Posterize
*/
pack.addFormula({
name: "Posterize",
description: "Limit the number of colors in a picture using color quantization, which reduces the distinct colors in an image while maintaining a visually-similar image.",
parameters: [
IMAGE_PARAM,
coda.makeParameter({
type: coda.ParameterType.Number,
name: "colors",
description: "The number of distinct colors to reduce the image palette to. Valid values range from 2 to 256.",
})
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([imageUrl, colors]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
return coda.withQueryParams(utils.cleanUpImageUrl(imageUrl), {
'colorquant' : utils.validateParameterRange(colors, 2, 256)
});
},
});
/**
* Pixelate
*/
pack.addFormula({
name: "Pixelate",
description: "Apply a square pixellation effect to the image.",
parameters: [
IMAGE_PARAM,
coda.makeParameter({
type: coda.ParameterType.Number,
name: "size",
description: utils.makeAdjustmentDescription("The size of pixelation squares.", 0, 100, 0)
})
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([imageUrl, size]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
if (size == 0) { return imageUrl; }
return coda.withQueryParams(utils.cleanUpImageUrl(imageUrl), {
'px' : utils.validateParameterRange(size, 0, 2000)
});
},
});
/**
* Blur
*/
pack.addFormula({
name: "Blur",
description: "Apply a Gaussian style blur to the image, smoothing out image noise.",
parameters: [
IMAGE_PARAM,
coda.makeParameter({
type: coda.ParameterType.Number,
name: "radius",
description: utils.makeAdjustmentDescription("Blur radius.", 0, 2000, 0)
})
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([imageUrl, radius]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
if (radius == 0) { return imageUrl; }
return coda.withQueryParams(utils.cleanUpImageUrl(imageUrl), {
'blur' : utils.validateParameterRange(radius, 0, 2000)
});
},
});
// ------------------------------------------------------------------------------------------------------------------------------
// #endregion
// #region 3. Size, rotation, cropping
// ------------------------------------------------------------------------------------------------------------------------------
const MARK_FIT_MODES = [ "Clip", "Crop", "Max", "Scale" ];
const BLEND_FIT_MODES = [ ...MARK_FIT_MODES, "Clamp" ];
const IMAGE_FIT_MODES = [ ...BLEND_FIT_MODES, "Fill", "Fillmax", "Max", "Min" ];
// above, face area fit is ignored because it will be exposed through a separate method
const BLEND_CROP_MODES = [ "Top Left", "Top", "Top Right", "Left", "Center", "Right", "Bottom Left", "Bottom", "Bottom Right" ]; // no faces
const IMAGE_CROP_MODES = [ ...BLEND_CROP_MODES, "Focal Point", "Edges", "Entropy" ];
// TODO: Methods for manipulating the base image
// ------------------------------------------------------------------------------------------------------------------------------
// #endregion
// #region 4. Typesetting
// ------------------------------------------------------------------------------------------------------------------------------
const FONTS = [{"p":"American Typewriter Condensed Light","t":"American Typewriter Condensed Light"},{"p":"American Typewriter","t":"American Typewriter"},{"p":"American Typewriter Condensed,Bold","t":"American Typewriter Condensed Bold"},{"p":"American Typewriter Light","t":"American Typewriter Light"},{"p":"American Typewriter Condensed","t":"American Typewriter Condensed"},{"p":"American Typewriter,Bold","t":"American Typewriter Bold"},{"p":"AndaleMono","t":"Andale Mono"},{"p":"Arial-Black","t":"Arial Black"},{"p":"Arial,BoldItalicMT","t":"Arial Bold Italic"},{"p":"Arial,BoldMT","t":"Arial Bold"},{"p":"Arial,ItalicMT","t":"Arial Italic"},{"p":"Arial Narrow,BoldItalic","t":"Arial Narrow Bold Italic"},{"p":"Arial Narrow,Bold","t":"Arial Narrow Bold"},{"p":"Arial Narrow,Italic","t":"Arial Narrow Italic"},{"p":"Arial Narrow","t":"Arial Narrow"},{"p":"Arial Rounded MT,Bold","t":"Arial Rounded Bold"},{"p":"Arial UnicodeMS","t":"Arial Unicode"},{"p":"ArialMT","t":"Arial"},{"p":"Athelas,Italic","t":"Athelas Italic"},{"p":"Athelas,Bold","t":"Athelas Bold"},{"p":"Athelas-Regular","t":"Athelas Regular"},{"p":"Athelas,BoldItalic","t":"Athelas Bold Italic"},{"p":"STBaoli-SC-Regular","t":"Baoli Regular"},{"p":"Baskerville","t":"Baskerville"},{"p":"Baskerville,Bold","t":"Baskerville Bold"},{"p":"Baskerville,Italic","t":"Baskerville Italic"},{"p":"Baskerville,BoldItalic","t":"Baskerville Bold Italic"},{"p":"BigCaslon-Medium","t":"Big Caslon Medium"},{"p":"BrushScriptMT","t":"Brush Script MT"},{"p":"Chalkboard","t":"Chalkboard"},{"p":"Chalkboard,Bold","t":"Chalkboard Bold"},{"p":"Chalkboard SE Light","t":"Chalkboard SE Light"},{"p":"Chalkboard SE Regular","t":"Chalkboard SE Regular"},{"p":"Chalkboard SE,Bold","t":"Chalkboard SE Bold"},{"p":"Chalkduster","t":"Chalkduster"},{"p":"CharcoalCY","t":"Charcoal"},{"p":"Charter,Bold","t":"Charter Bold"},{"p":"Charter,Italic","t":"Charter Italic"},{"p":"Charter Black,Italic","t":"Charter Black Italic"},{"p":"Charter-Black","t":"Charter Black"},{"p":"Charter,BoldItalic","t":"Charter Bold Italic"},{"p":"Charter-Roman","t":"Charter Roman"},{"p":"Cochin,Italic","t":"Cochin Italic"},{"p":"Cochin,BoldItalic","t":"Cochin Bold Italic"},{"p":"Cochin,Bold","t":"Cochin Bold"},{"p":"Cochin","t":"Cochin"},{"p":"Comic Sans MS,Bold","t":"Comic Sans Bold"},{"p":"ComicSansMS","t":"Comic Sans"},{"p":"Copperplate,Bold","t":"Copperplate Bold"},{"p":"Copperplate-Light","t":"Copperplate Light"},{"p":"Copperplate","t":"Copperplate"},{"p":"Courier New,BoldItalic","t":"Courier New Bold Italic"},{"p":"Courier New,Bold","t":"Courier New Bold"},{"p":"Courier New,Italic","t":"CourierNew Italic"},{"p":"CourierNewPSMT","t":"Courier New"},{"p":"DIN Alternate,Bold","t":"DIN Alternate Bold"},{"p":"DIN Condensed,Bold","t":"DIN Condensed Bold"},{"p":"Didot,Bold","t":"Didot,Bold"},{"p":"Didot","t":"Didot"},{"p":"Didot,Italic","t":"Didot Italic"},{"p":"Futura-Medium","t":"Futura Medium"},{"p":"Futura-CondensedMedium","t":"Futura Condensed Medium"},{"p":"Futura Medium,Italic","t":"Futura Medium Italic"},{"p":"GenevaCyr","t":"Geneva"},{"p":"Georgia,BoldItalic","t":"Georgia Bold Italic"},{"p":"Georgia,Bold","t":"Georgia Bold"},{"p":"Georgia,Italic","t":"Georgia Italic"},{"p":"Georgia","t":"Georgia"},{"p":"Gill Sans,Bold","t":"Gill Sans Bold"},{"p":"Gill Sans","t":"Gill Sans"},{"p":"Gill Sans,UltraBold","t":"Gill Sans Ultra Bold"},{"p":"Gill Sans Light,Italic","t":"Gill Sans Light Italic"},{"p":"GillSans-Light","t":"Gill Sans Light"},{"p":"Gill Sans,BoldItalic","t":"Gill Sans Bold Italic"},{"p":"GillSans,Italic","t":"Gill Sans,Italic"},{"p":"Helvetica CY,Bold","t":"Helvetica CY Bold"},{"p":"HelveticaCY-Oblique","t":"Helvetica CY Oblique"},{"p":"HelveticaCY-Plain","t":"Helvetica CY Plain"},{"p":"Herculanum","t":"Herculanum"},{"p":"HoeflerText-Ornaments","t":""},{"p":"HoeflerText-Regular","t":"Hoefler Text Regular"},{"p":"Hoefler Text,Italic","t":"Hoefler Text Italic"},{"p":"HoeflerText-Black","t":"Hoefler Text Black"},{"p":"Hoefler Text Black,Italic","t":"Hoefler Text Black Italic"},{"p":"Impact","t":"Impact"},{"p":"IowanOldStyle-Titling","t":"IOWAN OLD STYLE tS"},{"p":"Iowan Old Style Black,Italic","t":"Iowan Old Style BlackItalic"},{"p":"IowanOldStyle-Black","t":"Iowan Old Style Black"},{"p":"Iowan Old Style,BoldItalic","t":"Iowan Old Style Bold Italic"},{"p":"IowanOldStyle-Roman","t":"Iowan Old Style Roman"},{"p":"Iowan Old Style,Bold","t":"Iowan Old Style Bold"},{"p":"Iowan Old Style,Italic","t":"Iowan Old Style Italic"},{"p":"Marion-Regular","t":"Marion Regular"},{"p":"Marion,Italic","t":"Marion Italic"},{"p":"Marion,Bold","t":"Marion Bold"},{"p":"PT Mono,Bold","t":"PT Mono Bold"},{"p":"PTMono-Regular","t":"PT Mono Regular"},{"p":"PTSans-Regular","t":"PT Sans Regular"},{"p":"PT Sans,BoldItalic","t":"PT Sans Bold Italic"},{"p":"PTSans-Caption","t":"PT Sans Caption"},{"p":"PT Sans Caption,Bold","t":"PT Sans Caption Bold"},{"p":"PTSans-Narrow","t":"PT Sans Narrow"},{"p":"PT Sans,Italic","t":"PT Sans Italic"},{"p":"PT Sans,Bold","t":"PT Sans Bold"},{"p":"PT Sans Narrow,Bold","t":"PT Sans Narrow Bold"},{"p":"PT Serif,BoldItalic","t":"PT Serif Bold Italic"},{"p":"PTSerif-Regular","t":"PT Serif Regular"},{"p":"PT Serif,Bold","t":"PT Serif Bold"},{"p":"PT Serif,Italic","t":"PT Serif Italic"},{"p":"PTSerif-Caption","t":"PT Serif Caption"},{"p":"PT Serif Caption,Italic","t":"PT Serif Caption Italic"},{"p":"Papyrus","t":"Papyrus"},{"p":"Papyrus-Condensed","t":"Papyrus Condensed"},{"p":"PlantagenetCherokee","t":"ᎠᎡᎢᎣᎤᎥᎦᎧᎨᎩᎪᎫᎬᎭᎮᎯᎰᎱᎲᎳᎴᎵᎶᎷᎸᎹᎺᎻᎼᎽᎾᎿᏀᏁᏂ"},{"p":"SavoyeLetPlain","t":"Savoye Plain"},{"p":"Seravek-Light","t":"Seravek Light"},{"p":"Seravek ExtraLight, Italic","t":"Seravek Extra Light Italic"},{"p":"Seravek,Italic","t":"Seravek Italic"},{"p":"Seravek,Bold","t":"Seravek Bold"},{"p":"Seravek","t":"Seravek"},{"p":"Seravek,BoldItalic","t":"Seravek Bold Italic"},{"p":"Seravek Light,Italic","t":"Seravek Light Italic"},{"p":"Seravek-ExtraLight","t":"Seravek Extra Light"},{"p":"Seravek Medium,Italic","t":"Seravek Medium Italic"},{"p":"Seravek-Medium","t":"Seravek Medium"},{"p":"Skia-Regular_Black-Extended","t":"Skia Regular Black-Extended"},{"p":"Skia-Regular_Light","t":"Skia Regular Light"},{"p":"Skia-Regular_Light-Extended","t":"Skia Regular Light-Extended"},{"p":"Skia-Regular_Light-Condensed","t":"Skia Regular Light-Condensed"},{"p":"Skia-Regular_Extended","t":"Skia Regular Extended"},{"p":"Skia-Regular_Black","t":"Skia Regular Black"},{"p":"Skia-Regular_Condensed","t":"Skia Regular Condensed"},{"p":"Skia-Regular","t":"Skia-Regular"},{"p":"Skia-Regular_Black-Condensed","t":"Skia Regular Black Condensed"},{"p":"SnellRoundhand","t":"Snell Roundhand"},{"p":"SnellRoundhand-Black","t":"Snell Roundhand Black"},{"p":"Snell Roundhand,Bold","t":"Snell Roundhand Bold"},{"p":"Superclarendon-Black","t":"Superclarendon Black"},{"p":"Superclarendon,Italic","t":"Superclarendon Italic"},{"p":"Superclarendon-Light","t":"Superclarendon Light"},{"p":"Superclarendon,Bold","t":"Superclarendon Bold"},{"p":"Superclarendon Light,Italic","t":"Superclarendon Light Italic"},{"p":"Superclarendon-Regular","t":"Superclarendon Regular"},{"p":"Superclarendon Black,Italic","t":"Superclarendon Black Italic"},{"p":"Superclarendon,BoldItalic","t":"Superclarendon Bold Italic"},{"p":"Tahoma,Bold","t":"Tahoma Bold"},{"p":"Tahoma","t":"Tahoma"},{"p":"Times New Roman,BoldItalic","t":"Times New Roman Bold Italic"},{"p":"Times New Roman,Bold","t":"Times New Roman Bold"},{"p":"Times New Roman,Italic","t":"Times New Roman Italic"},{"p":"TimesNewRomanPSMT","t":"Times New Roman"},{"p":"Trebuchet MS,BoldItalic","t":"Trebuchet Bold Italic"},{"p":"Trebuchet MS,Bold","t":"Trebuchet Bold"},{"p":"Trebuchet MS,Italic","t":"Trebuchet,Italic"},{"p":"TrebuchetMS","t":"Trebuchet"},{"p":"Verdana,BoldItalic","t":"Verdana Bold Italic"},{"p":"Verdana,Bold","t":"Verdana Bold"},{"p":"Verdana,Italic","t":"Verdana Italic"},{"p":"Verdana","t":"Verdana"},{"p":"Waseem","t":"ش ص ض ط ظ ع غ ف ق ك ل م ن هـ و ي ١٢٣٤٥٦٧٨٩٠"},{"p":"WaseemLight","t":"ش ص ض ط ظ ع غ ف ق ك ل م ن هـ و ي ١٢٣٤٥٦٧٨٩٠"},{"p":"Webdings","t":""},{"p":"Wingdings2","t":""},{"p":"Wingdings3","t":""},{"p":"Wingdings-Regular","t":""},{"p":"Yuanti SC,Bold","t":"Yuanti SC Bold"},{"p":"STYuanti-SC-Light","t":"Yuanti SC Light"},{"p":"STYuanti-SC-Regular","t":"Yuanti SC Regular"},{"p":"YuppySC-Regular","t":"Yuppy SC Regular"},{"p":"Zapfino","t":"Zapfino"},{"p":"Avenir Next Condensed Ultra Light,Italic","t":"Avenir Next Condensed Ultra Light Italic"},{"p":"Avenir Next Condensed Medium","t":"Avenir Next Condensed Medium"},{"p":"Avenir Next Condensed,Bold","t":"Avenir Next Condensed Bold"},{"p":"Avenir Next Condensed Ultra Light","t":"Avenir Next Condensed Ultra Light"},{"p":"Avenir Next Condensed Heavy,Italic","t":"Avenir Next Condensed Heavy Italic"},{"p":"Avenir Next Condensed Heavy","t":"Avenir Next Condensed Heavy"},{"p":"Avenir Next Condensed Demi,Bold","t":"Avenir Next Condensed DemiBold"},{"p":"Avenir Next Condensed Regular","t":"Avenir Next Condensed Regular"},{"p":"Avenir Next Condensed Demi,BoldItalic","t":"Avenir Next Condensed DemiBold Italic"},{"p":"Avenir Next Condensed,BoldItalic","t":"Avenir Next Condensed Bold Italic"},{"p":"Avenir Next Condensed Medium,Italic","t":"Avenir Next Condensed Medium Italic"},{"p":"Avenir Next Condensed,Italic","t":"Avenir Next Condensed Italic"},{"p":"Avenir Next Ultra Light","t":"Avenir Next Ultra Light"},{"p":"Avenir Next Heavy,Italic","t":"Avenir Next Heavy Italic"},{"p":"Avenir Next Demi,BoldItalic","t":"Avenir Next DemiBold Italic"},{"p":"Avenir Next Demi,Bold","t":"Avenir Next DemiBold"},{"p":"Avenir Next,Italic","t":"Avenir Next Italic"},{"p":"Avenir Next,BoldItalic","t":"Avenir Next Bold Italic"},{"p":"Avenir Next Ultra Light,Italic","t":"Avenir Next Ultra Light Italic"},{"p":"Avenir Next Heavy","t":"Avenir Next Heavy"},{"p":"Avenir Next Regular","t":"Avenir Next Regular"},{"p":"Avenir Next Medium,Italic","t":"Avenir Next Medium Italic"},{"p":"Avenir Next Medium","t":"Avenir Next Medium"},{"p":"Avenir Next,Bold","t":"Avenir Next Bold"},{"p":"Avenir-Roman","t":"Avenir Roman"},{"p":"Avenir-Black","t":"Avenir Black"},{"p":"Avenir-MediumOblique","t":"Avenir Medium Oblique"},{"p":"Avenir-Heavy","t":"Avenir Heavy"},{"p":"Avenir-LightOblique","t":"Avenir Light Oblique"},{"p":"Avenir-BlackOblique","t":"Avenir Black Oblique"},{"p":"Avenir-Book","t":"Avenir Book"},{"p":"Avenir-HeavyOblique","t":"Avenir Heavy Oblique"},{"p":"Avenir-Light","t":"Avenir Light"},{"p":"Avenir-BookOblique","t":"Avenir Book Oblique"},{"p":"Avenir-Medium","t":"Avenir Medium"},{"p":"Avenir-Oblique","t":"Avenir Oblique"},{"p":"Courier-Oblique","t":"Courier Oblique"},{"p":"Courier","t":"Courier"},{"p":"Courier,Bold","t":"Courier Bold"},{"p":"Geneva","t":"Geneva"},{"p":"Helvetica","t":"Helvetica"},{"p":"Helvetica-Light","t":"Helvetica Light"},{"p":"Helvetica,Bold","t":"Helvetica Bold"},{"p":"Helvetica-Oblique","t":"Helvetica Oblique"},{"p":"Helvetica-LightOblique","t":"Helvetica Light Oblique"},{"p":"Helvetica Neue UltraLight,Italic","t":"Helvetica Neue UltraLight Italic"},{"p":"Helvetica Neue,BoldItalic","t":"Helvetica Neue Bold Italic"},{"p":"Helvetica Neue Medium,Italic","t":"Helvetica Neue Medium,Italic"},{"p":"Helvetica Neue,Bold","t":"Helvetica Neue Bold"},{"p":"Helvetica Neue Condensed,Bold","t":"Helvetica Neue Condensed Bold"},{"p":"Helvetica Neue Thin,Italic","t":"Helvetica Neue Thin Italic"},{"p":"Helvetica Neue Thin","t":"Helvetica Neue Thin"},{"p":"Helvetica Neue Medium","t":"Helvetica Neue Medium"},{"p":"Helvetica Neue Light","t":"Helvetica Neue Light"},{"p":"Helvetica Neue Light,Italic","t":"Helvetica Neue Light Italic"},{"p":"Helvetica Neue UltraLight","t":"Helvetica Neue UltraLight"},{"p":"Helvetica Neue Condensed Black","t":"Helvetica Neue Condensed Black"},{"p":"Helvetica Neue","t":"Helvetica Neue"},{"p":"Helvetica Neue,Italic","t":"Helvetica Neue Italic"},{"p":"Lucida Grande,Bold","t":"Lucida Grande Bold"},{"p":"Lucida Grande","t":"Lucida Grande"},{"p":"Marker Felt Thin","t":"Marker Felt Thin"},{"p":"Marker Felt Wide","t":"Marker Felt Wide"},{"p":"Menlo,Bold","t":"Menlo Bold"},{"p":"Menlo-Regular","t":"Menlo"},{"p":"Menlo,BoldItalic","t":"Menlo Bold Italic"},{"p":"Menlo,Italic","t":"Menlo Italic"},{"p":"Monaco","t":"Monaco"},{"p":"Noteworthy-Light","t":"Noteworthy Light"},{"p":"Noteworthy,Bold","t":"Noteworthy Bold"},{"p":"Optima,Italic","t":"Optima Italic"},{"p":"Optima-Regular","t":"Optima"},{"p":"Optima-ExtraBlack","t":"Optima Extra Black"},{"p":"Optima,Bold","t":"Optima Bold"},{"p":"Optima,BoldItalic","t":"Optima Bold Italic"},{"p":"Palatino,Bold","t":"Palatino Bold"},{"p":"Palatino-Roman","t":"Palatino"},{"p":"Palatino,BoldItalic","t":"Palatino Bold Italic"},{"p":"Palatino,Italic","t":"Palatino Italic"},{"p":"Times,Bold","t":"Times Bold"},{"p":"Times,BoldItalic","t":"Times Bold Italic"},{"p":"Times,Italic","t":"Times Italic"},{"p":"Times-Roman","t":"Times Roman"},{"p":"Zapf Dingbats","t":"✁✂✃✄✆✇✈✉✌✍✎✏✐"}];
const FONT_AUTOCOMPLETE_OPTIONS = [ "serif", "sans-serif", "monospace", "cursive", "fantasy", "bold", "italic" ]
const HALIGN_MODES = ["Left", "Center", "Right"];
const VALIGN_MODES = ["Top", "Middle", "Bottom"];
const TEXT_PARAM = coda.makeParameter({
type: coda.ParameterType.String,
name: "text",
description: "The text string to overlay on the image. Supports UTF-8 characters, non-Latin character sets and emoji.",
});
const TEXT_COMMON_OPTIONAL_PARAMS = [
coda.makeParameter({
type: coda.ParameterType.String,
name: "font",
description: "Defines the typeface and style to set the text in. The value may be either a CSS-style category, or an explicit font name. The possible CSS names are serif, sans-serif, monospace, cursive, fantasy, optionally combined with “,bold” and/or “,italic” for respective styling. For an explicit typeface use the name instead. You can get the list of available typefaces by importing the Fonts table from this pack. Default is Helvetica.",
optional: true,
autocomplete: FONT_AUTOCOMPLETE_OPTIONS
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "fontSize",
description: "Sets the font size of the text, in pixels. Must be greater that 0. The default value is 12.",
optional: true
}),
coda.makeParameter({
type: coda.ParameterType.String,
name: "color",
description: "Sets the color of the text. Valid values are a color keyword or a 3- (RGB), 4- (ARGB), 6- (RRGGBB), or 8-digit (AARRGGBB) hexadecimal value. The “A” represents the color’s alpha transparency. The default is opaque black, 000.",
optional: true
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "outlineWidth",
description: "Sets the width of the outline to draw around the text. By default, this value is 0, which draws no outline. If defined, the outline is drawn centered to the letter contours — that is, half outside and half inside the text.",
optional: true
}),
coda.makeParameter({
type: coda.ParameterType.String,
name: "outlineColor",
description: "Sets the color of the outline to draw around the text, if txt-line is set. Valid values are a color keyword or a 3- (RGB), 4- (ARGB), 6- (RRGGBB), or 8-digit (AARRGGBB) hexadecimal value. The “A” represents the color’s alpha transparency. The default is opaque white, FFF.",
optional: true
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "shadowStrength",
description: "Places the shadow under the text. The shadow is faint blurred black and has no offset. It can be used to subtly separate light text from a light background. The default is 0 (no shadow) and the range is from 0 to 10.",
optional: true
}),
coda.makeParameter({
type: coda.ParameterType.String,
name: "horizontalPosition",
description: "Horizontal alignment of the text overlay within the image. Possible values are Left, Center, Right, or an integer number to use as a specific X coordinate in relation to the left edge. The default is Left.",
optional: true,
autocomplete: HALIGN_MODES
}),
coda.makeParameter({
type: coda.ParameterType.String,
name: "verticalPosition",
description: "Vertical alignment of the text overlay within the image. Possible values are Top, Middle, Bottom, or an integer number to use as a specific Y coordinate in relation to the top edge. The default is Top.",
optional: true,
autocomplete: VALIGN_MODES
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "padding",
description: "The amount of spacing (in pixels) to add between text and image edges. The default is 10. Setting horizontal and/or vertical position in pixels overrides padding along that dimension.",
optional: true
})
]
const TEXT_BEHAVIORS = [ "Fit", "Clip start", "Ellipsize start", "Clip middle", "Ellipsize middle", "Clip end", "Ellipsize end" ];
const TEXT_CWIDTH_PARAM = coda.makeParameter({
type: coda.ParameterType.Number,
name: "constraintWidth",
description: "The width (in pixels) to restrict the width of the text to. The default value is calculated by the width of the output image, minus any padding on the left and right. Use the “constraintBehavior” parameter to control what happens if the text exceeds given width.",
optional: true
});
const TEXT_CBEHAVIOR_PARAM = coda.makeParameter({
type: coda.ParameterType.String,
name: "constraintBehavior",
description: "Determines what should be done when text doesn’t fit given width. Possible options are to cut the text by character in the beginning, middle, or end, optionally with an … ellipsis, or scale it down to fit. Default value is to Clip end.",
optional: true,
autocomplete: TEXT_BEHAVIORS
});
const TEXT_LIGATURE_MODES = [ "None", "Common", "All" ];
const TEXT_LIGATURE_PARAM = coda.makeParameter({
type: coda.ParameterType.String,
name: "ligatures",
description: "Enables support for ligatures — special characters that combine two or more characters into a single character — if the selected font supports them. Default setting is Common ligatures.",
optional: true,
autocomplete: TEXT_LIGATURE_MODES
});
const TYPESETTING_ENDPOINT_PRIORITY_PARAMS = [
coda.makeParameter({
type: coda.ParameterType.Number,
name: "width",
description: "The width of the output text block. If not specified, the width will be 200px and long lines will wrap.",
optional: true
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "height",
description: "The height of the output text block. If not specified, the height will be at least 20px and expand to accommodate all lines of text.",
optional: true
}),
coda.makeParameter({
type: coda.ParameterType.String,
name: "backgroundColor",
description: "The background color of the text block. If not specified, the background will be fully transparent. Valid values are a color keyword or a 3- (RGB), 4- (ARGB), 6- (RRGGBB), or 8-digit (AARRGGBB) hexadecimal value. The “A” represents the color’s alpha transparency.",
optional: true
}),
];
const TYPESETTING_ENDPOINT_EXTRA_PARAMS = [
coda.makeParameter({
type: coda.ParameterType.Number,
name: "leading",
description: "Adjusts spacing between lines in a multi-line text image. The default is 0, which corresponds to approximately 120% line height. Negative values are not allowed.",
optional: true
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "tracking",
description: "Adjusts spacing between letters. Default is 0, and negative values are allowed.",
optional: true
})
]
/**
* A sync table of all available fonts
*/
pack.addSyncTable({
name: "Fonts",
description: "A selection of fonts available to use with the Edit Images pack",
identityName: "Font",
schema: coda.makeObjectSchema({
properties: {
name : { type: coda.ValueType.String },
sampleText: { type: coda.ValueType.String },
specimen: { type: coda.ValueType.String, codaType: coda.ValueHintType.ImageReference }
},
idProperty: "name",
displayProperty: "name",
featuredProperties: ["name", "specimen"],
attribution: [
{
type: coda.AttributionNodeType.Image,
imageUrl: "https://imgix.com/favicon.ico",
anchorUrl: "https://imgix.com",
},
{
type: coda.AttributionNodeType.Text,
text: "Provided by",
},
{
type: coda.AttributionNodeType.Link,
anchorText: "imgix",
anchorUrl: "https://imgix.com",
}
],
}),
formula: {
name: "SyncAvailableFonts",
description: "Sync the table of available fonts and their specimens",
connectionRequirement: coda.ConnectionRequirement.None,
parameters: [],
execute: async function () {
let result = FONTS.sort((a, b) => a.p.localeCompare(b.p)).map(item => {
return {
name: item.p,
sampleText: item.t,
specimen: coda.withQueryParams("https://assets.imgix.net/~text", {
"txt": item.t,
"txt-font" : item.p,
"w": 300,
"txt-size" : 20,
"txt-pad" : 0
})
}
});
return {
result
};
},
},
});
/**
* SetTextOverlay - a single line
*/
pack.addFormula({
name: "SetTextOverlay",
description: "Add a single line text overlay to the image, with positioning and a full range of formatting properties. Only one text overlay per image (layer) can be added. For rendering multi-line text boxes that can be composed onto images use TextBox() formula.",
parameters: [
IMAGE_PARAM,
TEXT_PARAM,
...TEXT_COMMON_OPTIONAL_PARAMS,
TEXT_CWIDTH_PARAM,
TEXT_CBEHAVIOR_PARAM,
TEXT_LIGATURE_PARAM
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([imageUrl, text, font, fontSize, color, outlineWidth, outlineColor, shadowStrength, horizontalPosition, verticalPosition, padding, constraintWidth, constraintBehavior, ligatures]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
// if text already set, throw an error
if (imageUrl.match(/txt(?:-[a-z]+)?=/)) {
throw new coda.UserVisibleError("The image already has a text overlay set. You cannot add multiple text overlays to the same image — that’s the limitation of the rendering platform. Consider making multiple layers and composing them with Composition() formula.")
}
if (text.length === 0) {
return imageUrl;
}
// Common args
let commonArgs = utils.validateAndApplyCommonTextParams(font, fontSize, color, outlineWidth, outlineColor, shadowStrength, horizontalPosition, verticalPosition, padding, ligatures);
// Text overlay specific args
if (constraintWidth !== undefined && constraintWidth <= 0) {
throw new coda.UserVisibleError("Invalid constraint width. Valid values are larger than 0.");
}
let clip, fit;
if (constraintBehavior !== undefined) {
if (constraintBehavior === "Fit") {
fit = "max";
} else if (TEXT_BEHAVIORS.includes(constraintBehavior)) {
let ellipsis = constraintBehavior.toLowerCase().includes('ellipsize');
let clipSide = constraintBehavior.match(/start|middle|end/)[0];
clip = clipSide + (ellipsis ? ",ellipsis" : "");
} else {
throw new coda.UserVisibleError("Invalid constraint behavior. Valid values are Fit and combinations of Clip/Ellipsize and Start/Middle/End.");
}
}
let args = {
'txt64' : new Buffer(text).toString('base64'),
...commonArgs
};
if (constraintWidth !== undefined) { args['txt-width'] = constraintWidth; }
if (clip !== undefined) { args['txt-clip'] = clip; }
if (fit !== undefined) { args['txt-fit'] = fit; }
return coda.withQueryParams(utils.cleanUpImageUrl(imageUrl), args);
}
});
/**
* MakeTextBlock - standalone blendable image
*/
pack.addFormula({
name: "MakeTextBlock",
description: "Create a multi-line block of text with the ability to set its dimensions and background color. The result can be an image on its own or used as an overlay to blend onto other image.",
parameters: [
TEXT_PARAM,
...TYPESETTING_ENDPOINT_PRIORITY_PARAMS,
...TEXT_COMMON_OPTIONAL_PARAMS,
...TYPESETTING_ENDPOINT_EXTRA_PARAMS,
TEXT_LIGATURE_PARAM
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([text, width, height, backgroundColor, font, fontSize, color, outlineWidth, outlineColor, shadowStrength, horizontalPosition, verticalPosition, padding, leading, tracking, ligatures]) {
// Empty text is actually allowed if one only wants to render a box
// Common args
let commonArgs = utils.validateAndApplyCommonTextParams(font, fontSize, color, outlineWidth, outlineColor, shadowStrength, horizontalPosition, verticalPosition, padding, ligatures);
if (width !== undefined && width < 0) {
throw new coda.UserVisibleError("Invalid width. Valid values are larger than 0.");
}
if (height !== undefined && height < 0) {
throw new coda.UserVisibleError("Invalid height. Valid values are larger than 0.");
}
if (backgroundColor !== undefined && !backgroundColor.match(/#?([0-9a-fA-F]?[0-9a-fA-F]{3}){1,2}/) && !COLOR_NAMES.includes(backgroundColor.toLowerCase())) {
throw new coda.UserVisibleError("Invalid background color. Use RGB, ARGB, RRGGBB, AARRGGBB, or one of the colors from the list of named colors: https://drafts.csswg.org/css-color/#named-colors");
}
if (leading !== undefined && leading < 0) {
throw new coda.UserVisibleError("Invalid leading. Valid values are larger than 0.");
}
if (tracking !== undefined && tracking < 0) {
throw new coda.UserVisibleError("Invalid tracking. Valid values are larger than 0.");
}
let args = {
'txt64' : new Buffer(<string>text).toString('base64'),
...commonArgs
};
if (width !== undefined) { args['w'] = width; }
if (height !== undefined) { args['h'] = height; }
if (backgroundColor !== undefined) { args['bg'] = backgroundColor.replace('#', ''); }
if (leading !== undefined) { args['txt-lead'] = leading; }
if (tracking !== undefined) { args['txt-track'] = tracking; }
return coda.withQueryParams("https://assets.imgix.net/~text", args);
}
});
// ------------------------------------------------------------------------------------------------------------------------------
// #endregion
// #region 4. Composition
// ------------------------------------------------------------------------------------------------------------------------------
// const TRANSPARENT_BASE_FHD_IMAGE_URL = "https://codaio.imgix.net/docs/ECF5jW5kVi/blobs/bl-dE3CGIUfnP/63770974bb82a7c571ea600bf970d9a4e1c0889ad6cfa6deceb9163ca6c8dae6fa0cfe528748789bac1edd5b4c5d0f1579a970d76641a61ad89028dd86123d9dbea0aa38cde3cd512e82b0c137277d63d151d2cce1aa8d5438c7740e61cb6b1672391e1a";
const TRANSPARENT_BASE_FHD_IMAGE_URL = "https://ct.imgix.net/px.png";
const LAYER_PARAMS_REGEX = /(?<=[?&])(layer-[^=]+)=([^&]+)&?/g;
// todo: add group names
const BLEND_MODES = [
"Normal",
"Multiply", "Darken", "Burn",
"Screen", "Lighten", "Dodge",
"Overlay", "Soft Light", "Hard Light",
"Difference", "Exclusion",
"Color", "Hue", "Saturation", "Luminosity"
];
const BLEND_COMMON_PARAMS = [
coda.makeParameter({
type: coda.ParameterType.String,
name: "blendMode",
description: "Determines how pixels from the overlay blend with the pixels of the underlying image. Normal mode simply draws one image (color) over another. For details on each blend mode check out https://docs.imgix.com/tutorials/dynamically-blending-images",
optional: true,
autocomplete: BLEND_MODES
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "opacity",
description: "Sets the level of opacity for an overlay image or color. Valid values are in the range from 0 to 100 where 0 is fully transparent and 100 is fully opaque. Default is 100.",
optional: true
}),
];
/**
* Blend modes sync table
*/
pack.addSyncTable({
name: "BlendModes",
description: "A selection of blend modes available to use with the Edit Images pack",
identityName: "BlendMode",
schema: coda.makeObjectSchema({
properties: {
mode: { type: coda.ValueType.String }
},
displayProperty: "mode",
idProperty: "mode",
featuredProperties: [ "mode" ]
}),
formula: {
name: "SyncBlendModes",
description: "Sync the selection of blend modes",
parameters: [],
execute: async function () {
let result = BLEND_MODES.map(mode => { return { mode } });
return { result };
},
}
});
/**
* Blend color - with mode
*/
pack.addFormula({
name: "BlendColor",
description: "Overlay a solid color over the image using one of blending modes.",
parameters: [
IMAGE_PARAM,
coda.makeParameter({
type: coda.ParameterType.String,
name: "color",
description: "A color keyword or a 3- (RGB), 4- (ARGB), 6- (RRGGBB), or 8-digit (AARRGGBB) hexadecimal value. The “A” represents the color’s alpha transparency and therefore the intensity of the monochromatic transformation.",
autocomplete: COLOR_NAMES
}),
...BLEND_COMMON_PARAMS
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: function ([imageUrl, color, blendMode, opacity]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
// if blend already set, throw an error
if (imageUrl.match(/blend(?:-[a-z]+)?=/)) {
throw new coda.UserVisibleError("The image already has a blend layer set. You cannot add multiple blends to the same image — that’s the limitation of the rendering platform. Consider downloading your blended image, uploading it as a new one, and blending on top of it. And to compose multiple layers with the simple Normal mode use the Composition() formula.")
}
color = color.replace('#', '');
if (!color.match(/([0-9a-fA-F]?[0-9a-fA-F]{3}){1,2}/) && !COLOR_NAMES.includes(color.toLowerCase())) {
throw new coda.UserVisibleError("Invalid color. Use RGB, ARGB, RRGGBB, AARRGGBB, or one of the colors from the list of named colors: https://drafts.csswg.org/css-color/#named-colors");
}
let args = {
'blend-color' : color.replace('#', ''),
'blend-mode' : blendMode ? blendMode.replace(' ', '').toLowerCase() : "overlay",
'blend-alpha' : utils.validateParameterRange(opacity, 0, 100)
}
return coda.withQueryParams(utils.cleanUpImageUrl(imageUrl), args);
}
});
// ---- Composition() and Layer() cut out because those may become a part of the paid offering ----
// ------------------------------------------------------------------------------------------------------------------------------
// #endregion
// #region 5. Export
// ------------------------------------------------------------------------------------------------------------------------------
const FILE_FORMATS = ["jpg", "png", "webp", "gif"];
const OPTIMIZATIONS = [ "None", "Compress", "Format", "Both" ];
const OPTIMIZATION_PARAMS = [
coda.makeParameter({
type: coda.ParameterType.String,
name: "optimize",
description: "Enable automatic optimizations for either output format and/or compression. Default is no optimizations.",
optional: true,
autocomplete: OPTIMIZATIONS
}),
coda.makeParameter({
type: coda.ParameterType.Number,
name: "quality",
description: "Sets the quality of lossy output. Valid values are in the range from 0 to 100. The default is 75 if no Compress optimization is set, and 45 if set.",
optional: true
})
];
pack.addFormula({
name: "Optimize",
description: "Apply optimizations to the output image to reduce image size, usually at the cost of reducing quality. This must be applied after all edit formulas. Use this for rendering resulting images in the doc. Omit if you want to get lossless rendering.",
parameters: [
IMAGE_PARAM,
...OPTIMIZATION_PARAMS
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.ImageAttachment,
execute: async function ([imageUrl, optimize, quality]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
if (optimize !== undefined && !OPTIMIZATIONS.includes(optimize)) {
throw new coda.UserVisibleError("Invalid optimize value. Valid values are None, Compress, Format, or Both.");
}
if (optimize === "Both") {
optimize = "compress,format";
}
let args = {};
// Find any "auto" params that are already used
let autoValue = imageUrl.match(/(?<=[?&]auto=)[^&$]+/);
if (!!autoValue) {
let stripped = autoValue[0].replace(/compress|format/g, '').replace(/(?<=^|,),+|,+$/g, '');
args['auto'] = stripped + "," + optimize;
} else if (optimize !== undefined && optimize !== "None") {
args['auto'] = optimize.toLowerCase();
}
if (quality !== undefined) { args['q'] = utils.validateParameterRange(quality, 0, 100); }
return coda.withQueryParams(imageUrl.replace("codahosted.io", "codaio.imgix.net"), args);
}
});
/**
* AsImageLink - just return the URL (imgix)
*/
pack.addFormula({
name: "AsImageLink",
description: "Return the resulting image as URL",
parameters: [
IMAGE_PARAM
],
resultType: coda.ValueType.String,
execute: async function ([imageUrl]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
return imageUrl.replace("codahosted.io", "codaio.imgix.net");
}
});
/**
* AsDownloadLink - return the link for OpenWindow() / Hyperlink()
*/
pack.addFormula({
name: "AsDownloadLink",
description: "Return a link that can be used with OpenWindow() or Hyperlink() to initiate the download of the resulting image. Any optimizations previously set on the image are turned off for maximum export quality. You can re-enable the optimizations explicitly.",
parameters: [
IMAGE_PARAM,
coda.makeParameter({
type: coda.ParameterType.String,
name: "fileName",
description: "Sets desired filename for the download. This should be the full name with extension (e.g. image.jpg). Supported formats are jpg, png, webp, and gif."
}),
...OPTIMIZATION_PARAMS
],
resultType: coda.ValueType.String,
codaType: coda.ValueHintType.Url,
execute: function ([imageUrl, filename, optimize, quality]) {
if (!imageUrl.match(HOSTED_IMAGE_URL_REGEX)) {
throw new coda.UserVisibleError(ERROR_NOT_IMAGE_COLUMN);
}
let format = filename.split('.').pop();
if (optimize !== undefined && !OPTIMIZATIONS.includes(optimize)) {
throw new coda.UserVisibleError("Invalid optimize value. Valid values are None, Compress, Format, or Both.");
} else if (optimize === "Both") {
optimize = "compress,format";
}
if (!FILE_FORMATS.includes(format.toLowerCase())) {
throw new coda.UserVisibleError("Invalid file format. Supported formats are jpg, png, webp, and gif.");
}
let args = {
dl : filename,
fm : format
};
// Find any "auto" params that are already used. Strip away compress and format if they were there
let autoValue = imageUrl.match(/(?<=[?&]auto=)[^&$]+/);
if (!!autoValue) {
let stripped = autoValue[0].replace(/compress|format/g, '').replace(/(?<=^|,),+|,+$/g, '');
args['auto'] = stripped + "," + optimize;
} else if (optimize !== undefined && optimize !== "None") {
args['auto'] = optimize.toLowerCase();
}
if (quality !== undefined) { args['q'] = utils.validateParameterRange(quality, 0, 100); }
return coda.withQueryParams(imageUrl.replace("codahosted.io", "codaio.imgix.net"), args);
},
});
// ------------------------------------------------------------------------------------------------------------------------------
// #endregion
// ------------------------------------------------------------------------------------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment