@mindful-web/image 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Parameter1 LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@mindful-web/image",
3
+ "version": "1.0.0",
4
+ "description": "Common image utilities for Mindful Web projects.",
5
+ "main": "src/index.js",
6
+ "author": "Jacob Bare <jacob@parameter1.com>",
7
+ "repository": "https://github.com/parameter1/mindful-web/tree/main/packages/image",
8
+ "license": "MIT",
9
+ "scripts": {
10
+ "lint:fix": "yarn lint --fix",
11
+ "lint": "eslint --ext .js --max-warnings 5 ./",
12
+ "test": "yarn lint"
13
+ },
14
+ "dependencies": {
15
+ "@mindful-web/html": "^1.0.0",
16
+ "@mindful-web/inflector": "^1.0.0"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "gitHead": "0b77cab713eb5841202bb86c7119949866bc68b5"
22
+ }
@@ -0,0 +1,183 @@
1
+ const { camelize } = require('@mindful-web/inflector');
2
+ const { URL, URLSearchParams } = require('url');
3
+
4
+ const params = [
5
+ // Adjustment
6
+ 'bri', // Brightness
7
+ 'con', // Contrast
8
+ 'exp', // Exposure
9
+ 'gam', // Gamma
10
+ 'high', // Highlight
11
+ 'hue', // Hue
12
+ 'invert', // Invert
13
+ 'sat', // Saturation
14
+ 'shad', // Shadow
15
+ 'sharp', // Sharpen
16
+ 'usm', // Unsharp Mask
17
+ 'usmrad', // Unsharp Mask Radius
18
+ 'vib', // Vibrance
19
+
20
+ // Automatic
21
+ 'auto', // Automatic
22
+
23
+ // Blending
24
+ 'ba', // Blend Align
25
+ 'balph', // Blend Alpha
26
+ 'bc', // Blend Crop
27
+ 'bf', // Blend Fit
28
+ 'bh', // Blend Height
29
+ 'blend', // Blend
30
+ 'bm', // Blend Mode
31
+ 'bp', // Blend Padding
32
+ 'bs', // Blend Size
33
+ 'bw', // Blend Width
34
+ 'bx', // Blend X Position
35
+ 'by', // Blend Y Position
36
+
37
+ // Border & Padding
38
+ 'border-radius-inner', // Inner Border Radius
39
+ 'border-radius', // Outer Border Radius
40
+ 'border', // Border Size Color
41
+ 'pad', // Padding
42
+
43
+ // Color Palette
44
+ 'colors', // Palette Color Count
45
+ 'palette', // Color Palette Extraction
46
+ 'prefix', // CSS Prefix
47
+
48
+ // Face Detection
49
+ 'facebindex', // Face Index
50
+ 'facepad', // Face Padding
51
+ 'faces', // JSON Face Data
52
+
53
+ // Fill
54
+ 'bg', // Background Color
55
+ 'fill-color', // Fill Color
56
+ 'fill-mode', // Fill Mode
57
+ 'fill', // Fill
58
+
59
+ // Focal Point Crop
60
+ 'fp-debug', // Focal Point Debug
61
+ 'fp-x', // Focal Point X Position
62
+ 'fp-y', // Focal Point Y Position
63
+ 'fp-z', // Focal Point Zoom
64
+
65
+ // Format
66
+ 'ch', // Client Hints
67
+ 'chromasub', // Chroma Subsampling
68
+ 'colorquant', // Color Quantization
69
+ 'cs', // Color Space
70
+ 'dl', // Download
71
+ 'dpi', // Dots Per Inch
72
+ 'fm', // Output Format
73
+ 'lossless', // Lossless Compression
74
+ 'q', // Output Quality
75
+
76
+ // Mask Image
77
+ 'corner-radius', // Mask Corner Radius
78
+ 'mask', // Mask Type
79
+ 'maskbg', // Mask Background Color
80
+
81
+ // Noise Reduction
82
+ 'nr', // Noise Reduction Bound
83
+ 'nrs', // Noise Reduction Sharpen
84
+
85
+ // PDF
86
+ 'page', // PDF Page Number
87
+
88
+ // Pixel Density
89
+ 'dpr', // Device Pixel Ratio
90
+
91
+ // Rotation
92
+ 'flip', // Flip Axis
93
+ 'or', // Orientation
94
+ 'rot', // Rotation
95
+
96
+ // Size
97
+ 'ar', // Aspect Ratio
98
+ 'crop', // Crop Mode
99
+ 'fit', // Resize Fit Mode
100
+ 'h', // Image Height
101
+ 'max-h', // Maximum Height
102
+ 'max-w', // Maximum Width
103
+ 'min-h', // Minimum Height
104
+ 'min-w', // Minimum Width
105
+ 'rect', // Source Rectangle Region
106
+ 'w', // Image Width
107
+
108
+ // Stylize
109
+ 'blur', // Gaussian Blur
110
+ 'duotone-alpha', // Duotone Alpha
111
+ 'duotone', // Duotone
112
+ 'htn', // Halftone
113
+ 'mono', // Monochrome
114
+ 'px', // Pixellate
115
+ 'sepia', // Sepia Tone
116
+
117
+ // Text
118
+ 'txt', // Text String
119
+ 'txtalign', // Text Align
120
+ 'txtclip', // Text Clipping Mode
121
+ 'txtclr', // Text Color
122
+ 'txtfit', // Text Fit Mode
123
+ 'txtfont', // Text Font
124
+ 'txtlig', // Text Ligatures
125
+ 'txtline', // Text Outline
126
+ 'txtlineclr', // Text Outline Color
127
+ 'txtpad', // Text Padding
128
+ 'txtshad', // Text Shadow
129
+ 'txtsize', // Text Font Size
130
+ 'txtwidth', // Text Width
131
+
132
+ // Trim
133
+ 'trim', // Trim Image
134
+ 'trimcolor', // Trim Color
135
+ 'trimmd', // Trim Mean Difference
136
+ 'trimsd', // Trim Standard Deviation
137
+ 'trimtol', // Trim Tolerance
138
+
139
+ // Watermark
140
+ 'mark', // Watermark Image URL
141
+ 'markalign', // Watermark Alignment Mode
142
+ 'markalpha', // Watermark Alpha
143
+ 'markbase', // Watermark Base URL
144
+ 'markfit', // Watermark Fit Mode
145
+ 'markh', // Watermark Height
146
+ 'markpad', // Watermark Padding
147
+ 'markscale', // Watermark Scale
148
+ 'markw', // Watermark Width
149
+ 'markx', // Watermark X Position
150
+ 'marky', // Watermark Y Position
151
+ ];
152
+
153
+ const coreDefaults = {
154
+ auto: 'format,compress',
155
+ h: 320,
156
+ w: 320,
157
+ };
158
+
159
+ module.exports = (src, selected, defaults, isLogo) => {
160
+ const options = { ...(selected || defaults || coreDefaults) };
161
+ if (isLogo) {
162
+ options.fit = 'fill';
163
+ options.fillColor = options.fillColor || 'fff';
164
+ options.bg = options.bg || 'fff';
165
+ options.pad = options.pad || '5';
166
+ }
167
+ try {
168
+ const url = new URL(src);
169
+ const searchParams = params.sort().reduce((sp, key) => {
170
+ const optVal = options[camelize(key)];
171
+ const searchVal = url.searchParams.get(key);
172
+ const value = optVal || searchVal;
173
+ if (value) sp.append(key, value);
174
+ return sp;
175
+ }, new URLSearchParams());
176
+ if (!searchParams.get('auto')) searchParams.set('auto', 'format,compress');
177
+ if (!searchParams.get('q')) searchParams.set('q', '70');
178
+ url.search = searchParams;
179
+ return url.toString();
180
+ } catch (e) {
181
+ return src;
182
+ }
183
+ };
@@ -0,0 +1,40 @@
1
+ const { titleize } = require('@mindful-web/inflector');
2
+ const { htmlEntities, stripHtml } = require('@mindful-web/html');
3
+
4
+ const altFrom = (value = '') => {
5
+ if (!value) return '';
6
+ const v = String(value);
7
+ const pos = v.lastIndexOf('.');
8
+ if (pos === -1) return v;
9
+ const offset = v.length - pos;
10
+ if (offset < 6) {
11
+ const replaced = v.replace(v.substring(pos), '');
12
+ const titleized = titleize(replaced);
13
+ return titleized.replace(/\./g, ' ');
14
+ }
15
+ return v;
16
+ };
17
+
18
+ const strip = (v) => htmlEntities.encode(stripHtml(v));
19
+
20
+ const clean = (v, { prepend, append }) => {
21
+ const values = [];
22
+ if (prepend) values.push(strip(prepend));
23
+ values.push((strip(v)));
24
+ if (append) values.push(strip(append));
25
+ return values.join(' ');
26
+ };
27
+
28
+ module.exports = ({
29
+ displayName,
30
+ caption,
31
+ name,
32
+ fileName,
33
+ prepend,
34
+ append,
35
+ } = {}) => {
36
+ if (displayName) return clean(displayName, { prepend, append });
37
+ if (caption) return clean(caption, { prepend, append });
38
+ if (name) return clean(altFrom(name), { prepend, append });
39
+ return clean(altFrom(fileName), { prepend, append });
40
+ };
@@ -0,0 +1,5 @@
1
+ module.exports = (caption) => {
2
+ if (!caption) return '';
3
+ // Replace new lines with <br> elements.
4
+ return `${caption}`.replace('\n', '<br>');
5
+ };
@@ -0,0 +1,15 @@
1
+ const buildImgixUrl = require('./build-imgix-url');
2
+
3
+ module.exports = (host, image, options, defaultOptions) => {
4
+ const {
5
+ filePath,
6
+ fileName,
7
+ cropDimensions,
8
+ } = image;
9
+ if (!fileName || !filePath) {
10
+ return buildImgixUrl(`https://${host}/asset-is-missing-file-name-or-path`, options, defaultOptions);
11
+ }
12
+ const path = cropDimensions && cropDimensions.aspectRatio ? `${filePath}/${cropDimensions.aspectRatio}` : filePath;
13
+ const src = `https://${host}/${path}/${fileName}`;
14
+ return buildImgixUrl(src, options, defaultOptions);
15
+ };
@@ -0,0 +1,77 @@
1
+ class CropRectangle {
2
+ constructor({
3
+ x,
4
+ y,
5
+ width,
6
+ height,
7
+ cropped,
8
+ } = {}) {
9
+ this.x = x;
10
+ this.y = y;
11
+ this.width = width;
12
+ this.height = height;
13
+ this.cropped = cropped;
14
+ }
15
+
16
+ isCropped() {
17
+ return this.cropped;
18
+ }
19
+
20
+ notCropped() {
21
+ return !this.cropped;
22
+ }
23
+
24
+ toString() {
25
+ return ['x', 'y', 'width', 'height'].map((key) => this[key]).join(',');
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Generates a crop rectangle for the given image width, height
31
+ * and crop dimensions.
32
+ *
33
+ * Uses the same scaling logic as Base Platform to properly calculate
34
+ * the crop area.
35
+ */
36
+ module.exports = ({ width, height, cropDimensions }) => {
37
+ if (!cropDimensions || !width || !height) {
38
+ return new CropRectangle({
39
+ x: 0,
40
+ y: 0,
41
+ width,
42
+ height,
43
+ cropped: false,
44
+ });
45
+ }
46
+
47
+ const coords = ['x1', 'x2', 'y1', 'y2'];
48
+ if (coords.some((key) => cropDimensions[key] == null || Number.isNaN(cropDimensions[key]))) {
49
+ return new CropRectangle({
50
+ x: 0,
51
+ y: 0,
52
+ width,
53
+ height,
54
+ cropped: false,
55
+ });
56
+ }
57
+
58
+ // @see Cygnus\ApplicationBundle\Apps\Management\Controller::cropImageAction
59
+ const scale = width / 640;
60
+ const {
61
+ x1,
62
+ x2,
63
+ y1,
64
+ y2,
65
+ } = coords.reduce((o, key) => {
66
+ const v = cropDimensions[key] > 0 ? Math.round(cropDimensions[key] * scale) : 0;
67
+ return { ...o, [key]: v };
68
+ }, {});
69
+
70
+ return new CropRectangle({
71
+ x: x1,
72
+ y: y1,
73
+ width: x2 - x1,
74
+ height: y2 - y1,
75
+ cropped: true,
76
+ });
77
+ };
@@ -0,0 +1,5 @@
1
+ module.exports = (width, ar, delimiter = ':') => {
2
+ const [x, y] = ar.split(delimiter);
3
+ const r = x / y;
4
+ return Math.round(width / r);
5
+ };
@@ -0,0 +1,5 @@
1
+ module.exports = (height, ar, delimiter = ':') => {
2
+ const [x, y] = ar.split(delimiter);
3
+ const r = x / y;
4
+ return Math.round(height * r);
5
+ };
@@ -0,0 +1,6 @@
1
+ module.exports = ({ width = 350, cropRectangle = {} } = {}) => {
2
+ if (!cropRectangle) return null;
3
+ const { width: w, height: h } = cropRectangle;
4
+ if (!w || !h) return null;
5
+ return Math.round((width / w) * h);
6
+ };
package/src/index.js ADDED
@@ -0,0 +1,19 @@
1
+ const buildImgixUrl = require('./build-imgix-url');
2
+ const createAltFor = require('./create-alt-for');
3
+ const createCaptionFor = require('./create-caption-for');
4
+ const createSrcFor = require('./create-src-for');
5
+ const cropRectangle = require('./crop-rectangle');
6
+ const getRelativeAspectRatioHeight = require('./get-relative-aspect-ratio-height');
7
+ const getRelativeAspectRatioWidth = require('./get-relative-aspect-ratio-width');
8
+ const getRelativeCropRectangleHeight = require('./get-relative-crop-rectangle-height');
9
+
10
+ module.exports = {
11
+ buildImgixUrl,
12
+ createAltFor,
13
+ createCaptionFor,
14
+ createSrcFor,
15
+ cropRectangle,
16
+ getRelativeAspectRatioHeight,
17
+ getRelativeAspectRatioWidth,
18
+ getRelativeCropRectangleHeight,
19
+ };