@kongyo2/image-to-pure-css 0.1.2
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 +21 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +49 -0
- package/dist/color-utils.d.ts +6 -0
- package/dist/color-utils.js +65 -0
- package/dist/css-assembler.d.ts +12 -0
- package/dist/css-assembler.js +53 -0
- package/dist/gradient-builder.d.ts +23 -0
- package/dist/gradient-builder.js +146 -0
- package/dist/image-reader.d.ts +6 -0
- package/dist/image-reader.js +31 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +11 -0
- package/dist/types.d.ts +20 -0
- package/dist/types.js +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kongyo2
|
|
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/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, resolve } from "node:path";
|
|
4
|
+
import { assembleCSS } from "./css-assembler.js";
|
|
5
|
+
import { readImage } from "./image-reader.js";
|
|
6
|
+
function parseArgs(argv) {
|
|
7
|
+
const args = argv.slice(2);
|
|
8
|
+
if (args.length === 0) {
|
|
9
|
+
console.error("Usage: kongyo-css <image> [--width N] [--tolerance N] [--output file.txt]");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const inputPath = resolve(args[0]);
|
|
13
|
+
let width;
|
|
14
|
+
let tolerance = 0;
|
|
15
|
+
let output;
|
|
16
|
+
for (let i = 1; i < args.length; i++) {
|
|
17
|
+
switch (args[i]) {
|
|
18
|
+
case "--width":
|
|
19
|
+
width = parseInt(args[++i], 10);
|
|
20
|
+
break;
|
|
21
|
+
case "--tolerance":
|
|
22
|
+
tolerance = parseInt(args[++i], 10);
|
|
23
|
+
break;
|
|
24
|
+
case "--output":
|
|
25
|
+
output = args[++i];
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (!output) {
|
|
30
|
+
output = resolve(basename(inputPath).replace(/\.[^.]+$/, ".txt"));
|
|
31
|
+
}
|
|
32
|
+
return { inputPath, width, tolerance, output };
|
|
33
|
+
}
|
|
34
|
+
async function main() {
|
|
35
|
+
const { inputPath, width, tolerance, output } = parseArgs(process.argv);
|
|
36
|
+
console.log(`Reading: ${inputPath}`);
|
|
37
|
+
const imageData = await readImage(inputPath, width);
|
|
38
|
+
console.log(`Size: ${imageData.width}x${imageData.height}`);
|
|
39
|
+
console.log(`Generating CSS (tolerance=${tolerance})...`);
|
|
40
|
+
const css = assembleCSS(imageData, tolerance);
|
|
41
|
+
const sizeKB = (css.length / 1024).toFixed(1);
|
|
42
|
+
console.log(`Output: ${sizeKB} KB (${css.length.toLocaleString()} chars)`);
|
|
43
|
+
writeFileSync(output, css, "utf-8");
|
|
44
|
+
console.log(`Written: ${output}`);
|
|
45
|
+
}
|
|
46
|
+
main().catch((err) => {
|
|
47
|
+
console.error(err);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS色圧縮ユーティリティ
|
|
3
|
+
* RGB値を最短のCSS色表現に変換する
|
|
4
|
+
*/
|
|
5
|
+
export declare function rgbToCompressedColor(r: number, g: number, b: number): string;
|
|
6
|
+
export declare function colorsAreSimilar(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number, tolerance: number): boolean;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS色圧縮ユーティリティ
|
|
3
|
+
* RGB値を最短のCSS色表現に変換する
|
|
4
|
+
*/
|
|
5
|
+
const HEX_TO_NAME = new Map([
|
|
6
|
+
["#ff0000", "red"],
|
|
7
|
+
["#d2b48c", "tan"],
|
|
8
|
+
["#ffd700", "gold"],
|
|
9
|
+
["#cd853f", "peru"],
|
|
10
|
+
["#ffc0cb", "pink"],
|
|
11
|
+
["#dda0dd", "plum"],
|
|
12
|
+
["#808080", "gray"],
|
|
13
|
+
["#000080", "navy"],
|
|
14
|
+
["#008080", "teal"],
|
|
15
|
+
["#fffafa", "snow"],
|
|
16
|
+
["#ff7f50", "coral"],
|
|
17
|
+
["#f0e68c", "khaki"],
|
|
18
|
+
["#f5deb3", "wheat"],
|
|
19
|
+
["#fffff0", "ivory"],
|
|
20
|
+
["#faf0e6", "linen"],
|
|
21
|
+
["#f5f5dc", "beige"],
|
|
22
|
+
["#808000", "olive"],
|
|
23
|
+
["#f0ffff", "azure"],
|
|
24
|
+
["#a52a2a", "brown"],
|
|
25
|
+
["#008000", "green"],
|
|
26
|
+
["#ffe4c4", "bisque"],
|
|
27
|
+
["#a0522d", "sienna"],
|
|
28
|
+
["#da70d6", "orchid"],
|
|
29
|
+
["#fa8072", "salmon"],
|
|
30
|
+
["#ee82ee", "violet"],
|
|
31
|
+
["#ff6347", "tomato"],
|
|
32
|
+
["#800000", "maroon"],
|
|
33
|
+
["#ffa500", "orange"],
|
|
34
|
+
["#c0c0c0", "silver"],
|
|
35
|
+
["#800080", "purple"],
|
|
36
|
+
["#4b0082", "indigo"],
|
|
37
|
+
]);
|
|
38
|
+
const hexCache = new Map();
|
|
39
|
+
function toHex2(n) {
|
|
40
|
+
return n.toString(16).padStart(2, "0");
|
|
41
|
+
}
|
|
42
|
+
export function rgbToCompressedColor(r, g, b) {
|
|
43
|
+
const key = (r << 16) | (g << 8) | b;
|
|
44
|
+
const cached = hexCache.get(key);
|
|
45
|
+
if (cached)
|
|
46
|
+
return cached;
|
|
47
|
+
const hex = `#${toHex2(r)}${toHex2(g)}${toHex2(b)}`;
|
|
48
|
+
const named = HEX_TO_NAME.get(hex);
|
|
49
|
+
if (named) {
|
|
50
|
+
hexCache.set(key, named);
|
|
51
|
+
return named;
|
|
52
|
+
}
|
|
53
|
+
if (hex[1] === hex[2] && hex[3] === hex[4] && hex[5] === hex[6]) {
|
|
54
|
+
const short = `#${hex[1]}${hex[3]}${hex[5]}`;
|
|
55
|
+
hexCache.set(key, short);
|
|
56
|
+
return short;
|
|
57
|
+
}
|
|
58
|
+
hexCache.set(key, hex);
|
|
59
|
+
return hex;
|
|
60
|
+
}
|
|
61
|
+
export function colorsAreSimilar(r1, g1, b1, r2, g2, b2, tolerance) {
|
|
62
|
+
return (Math.abs(r1 - r2) <= tolerance &&
|
|
63
|
+
Math.abs(g1 - g2) <= tolerance &&
|
|
64
|
+
Math.abs(b1 - b2) <= tolerance);
|
|
65
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS組み立てモジュール
|
|
3
|
+
* 全行のグラデーションを合成し、最終的なインラインスタイルのdivを生成する
|
|
4
|
+
*
|
|
5
|
+
* 最適化:
|
|
6
|
+
* - 最頻出色をbackground-colorに設定
|
|
7
|
+
* - 全ピクセルがbackground-colorの行はgradientをスキップ
|
|
8
|
+
* - background-size/repeatは全レイヤー共通値を1つだけ指定
|
|
9
|
+
*/
|
|
10
|
+
import type { ImageData } from "./types.js";
|
|
11
|
+
export declare function findDominantColor(imageData: ImageData): string;
|
|
12
|
+
export declare function assembleCSS(imageData: ImageData, tolerance: number): string;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS組み立てモジュール
|
|
3
|
+
* 全行のグラデーションを合成し、最終的なインラインスタイルのdivを生成する
|
|
4
|
+
*
|
|
5
|
+
* 最適化:
|
|
6
|
+
* - 最頻出色をbackground-colorに設定
|
|
7
|
+
* - 全ピクセルがbackground-colorの行はgradientをスキップ
|
|
8
|
+
* - background-size/repeatは全レイヤー共通値を1つだけ指定
|
|
9
|
+
*/
|
|
10
|
+
import { rgbToCompressedColor } from "./color-utils.js";
|
|
11
|
+
import { buildRowGradient } from "./gradient-builder.js";
|
|
12
|
+
export function findDominantColor(imageData) {
|
|
13
|
+
const colorCount = new Map();
|
|
14
|
+
for (const row of imageData.pixels) {
|
|
15
|
+
for (const pixel of row) {
|
|
16
|
+
const color = rgbToCompressedColor(pixel.r, pixel.g, pixel.b);
|
|
17
|
+
colorCount.set(color, (colorCount.get(color) ?? 0) + 1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
let maxCount = 0;
|
|
21
|
+
let dominant = "#000";
|
|
22
|
+
for (const [color, count] of colorCount) {
|
|
23
|
+
if (count > maxCount) {
|
|
24
|
+
maxCount = count;
|
|
25
|
+
dominant = color;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return dominant;
|
|
29
|
+
}
|
|
30
|
+
export function assembleCSS(imageData, tolerance) {
|
|
31
|
+
const { width, height, pixels } = imageData;
|
|
32
|
+
const bgColor = findDominantColor(imageData);
|
|
33
|
+
const gradients = [];
|
|
34
|
+
const positions = [];
|
|
35
|
+
for (let y = 0; y < height; y++) {
|
|
36
|
+
const result = buildRowGradient(pixels[y], tolerance);
|
|
37
|
+
if (result.isSolid && result.solidColor === bgColor) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
gradients.push(result.gradient);
|
|
41
|
+
positions.push(y === 0 ? "0 0" : `0 ${y}px`);
|
|
42
|
+
}
|
|
43
|
+
const parts = [
|
|
44
|
+
`width:${width}px`,
|
|
45
|
+
`height:${height}px`,
|
|
46
|
+
`background-color:${bgColor}`,
|
|
47
|
+
`background-image:${gradients.join(",")}`,
|
|
48
|
+
`background-size:100% 1px`,
|
|
49
|
+
`background-position:${positions.join(",")}`,
|
|
50
|
+
`background-repeat:no-repeat`,
|
|
51
|
+
];
|
|
52
|
+
return `<div style="${parts.join(";")}"></div>`;
|
|
53
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* グラデーション構築モジュール
|
|
3
|
+
*
|
|
4
|
+
* 2つの動作モード:
|
|
5
|
+
*
|
|
6
|
+
* 【Hard Stop モード (tolerance=0)】
|
|
7
|
+
* - 隣接同色ピクセルをRLE圧縮
|
|
8
|
+
* - CSS gradient の "0" クランプトリックでハードストップ生成
|
|
9
|
+
* - ピクセル完全再現
|
|
10
|
+
*
|
|
11
|
+
* 【Smooth モード (tolerance>0)】
|
|
12
|
+
* - 貪欲線形近似アルゴリズムで最適なストップ位置を検出
|
|
13
|
+
* - CSSのネイティブ線形補間を活用して滑らかな遷移を表現
|
|
14
|
+
* - グラデーション領域のストップ数を大幅に削減
|
|
15
|
+
* - エッジ部分は自動的に短いセグメントになり鮮明さを維持
|
|
16
|
+
*/
|
|
17
|
+
import type { RGB } from "./types.js";
|
|
18
|
+
export interface RowGradientResult {
|
|
19
|
+
gradient: string;
|
|
20
|
+
isSolid: boolean;
|
|
21
|
+
solidColor: string | null;
|
|
22
|
+
}
|
|
23
|
+
export declare function buildRowGradient(row: RGB[], tolerance: number): RowGradientResult;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* グラデーション構築モジュール
|
|
3
|
+
*
|
|
4
|
+
* 2つの動作モード:
|
|
5
|
+
*
|
|
6
|
+
* 【Hard Stop モード (tolerance=0)】
|
|
7
|
+
* - 隣接同色ピクセルをRLE圧縮
|
|
8
|
+
* - CSS gradient の "0" クランプトリックでハードストップ生成
|
|
9
|
+
* - ピクセル完全再現
|
|
10
|
+
*
|
|
11
|
+
* 【Smooth モード (tolerance>0)】
|
|
12
|
+
* - 貪欲線形近似アルゴリズムで最適なストップ位置を検出
|
|
13
|
+
* - CSSのネイティブ線形補間を活用して滑らかな遷移を表現
|
|
14
|
+
* - グラデーション領域のストップ数を大幅に削減
|
|
15
|
+
* - エッジ部分は自動的に短いセグメントになり鮮明さを維持
|
|
16
|
+
*/
|
|
17
|
+
import { rgbToCompressedColor } from "./color-utils.js";
|
|
18
|
+
// ==========================================
|
|
19
|
+
// Hard Stop モード (tolerance=0)
|
|
20
|
+
// ==========================================
|
|
21
|
+
function buildRowRuns(row) {
|
|
22
|
+
if (row.length === 0)
|
|
23
|
+
return [];
|
|
24
|
+
const runs = [];
|
|
25
|
+
let runColor = rgbToCompressedColor(row[0].r, row[0].g, row[0].b);
|
|
26
|
+
let runStart = 0;
|
|
27
|
+
for (let i = 1; i <= row.length; i++) {
|
|
28
|
+
const color = i < row.length ? rgbToCompressedColor(row[i].r, row[i].g, row[i].b) : null;
|
|
29
|
+
if (color !== runColor) {
|
|
30
|
+
runs.push({ color: runColor, start: runStart, end: i });
|
|
31
|
+
if (i < row.length && color !== null) {
|
|
32
|
+
runColor = color;
|
|
33
|
+
runStart = i;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return runs;
|
|
38
|
+
}
|
|
39
|
+
function runsToHardGradient(runs) {
|
|
40
|
+
if (runs.length === 1) {
|
|
41
|
+
const c = runs[0].color;
|
|
42
|
+
return `linear-gradient(${c},${c})`;
|
|
43
|
+
}
|
|
44
|
+
const stops = [];
|
|
45
|
+
for (let i = 0; i < runs.length; i++) {
|
|
46
|
+
const run = runs[i];
|
|
47
|
+
if (i === 0) {
|
|
48
|
+
stops.push(`${run.color} ${run.end}px`);
|
|
49
|
+
}
|
|
50
|
+
else if (i === runs.length - 1) {
|
|
51
|
+
stops.push(`${run.color} 0`);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
stops.push(`${run.color} 0 ${run.end}px`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return `linear-gradient(90deg,${stops.join(",")})`;
|
|
58
|
+
}
|
|
59
|
+
// ==========================================
|
|
60
|
+
// Smooth モード (tolerance>0)
|
|
61
|
+
// ==========================================
|
|
62
|
+
/**
|
|
63
|
+
* row[start]→row[end] の線形補間が中間ピクセル全てにtolerance以内で一致するか判定
|
|
64
|
+
*/
|
|
65
|
+
function canLinearApproximate(row, start, end, tolerance) {
|
|
66
|
+
if (end - start <= 1)
|
|
67
|
+
return true;
|
|
68
|
+
const sc = row[start];
|
|
69
|
+
const ec = row[end];
|
|
70
|
+
const span = end - start;
|
|
71
|
+
for (let j = start + 1; j < end; j++) {
|
|
72
|
+
const t = (j - start) / span;
|
|
73
|
+
const er = sc.r + (ec.r - sc.r) * t;
|
|
74
|
+
const eg = sc.g + (ec.g - sc.g) * t;
|
|
75
|
+
const eb = sc.b + (ec.b - sc.b) * t;
|
|
76
|
+
if (Math.abs(er - row[j].r) > tolerance ||
|
|
77
|
+
Math.abs(eg - row[j].g) > tolerance ||
|
|
78
|
+
Math.abs(eb - row[j].b) > tolerance) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 貪欲法で線形近似可能な最長セグメントを見つけ、最適なストップ位置を返す
|
|
86
|
+
*/
|
|
87
|
+
function findOptimalStops(row, tolerance) {
|
|
88
|
+
if (row.length <= 2) {
|
|
89
|
+
return row.length === 1 ? [0] : [0, row.length - 1];
|
|
90
|
+
}
|
|
91
|
+
const stops = [0];
|
|
92
|
+
let segStart = 0;
|
|
93
|
+
for (let candidate = 2; candidate < row.length; candidate++) {
|
|
94
|
+
if (!canLinearApproximate(row, segStart, candidate, tolerance)) {
|
|
95
|
+
stops.push(candidate - 1);
|
|
96
|
+
segStart = candidate - 1;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (stops[stops.length - 1] !== row.length - 1) {
|
|
100
|
+
stops.push(row.length - 1);
|
|
101
|
+
}
|
|
102
|
+
return stops;
|
|
103
|
+
}
|
|
104
|
+
function stopsToSmoothGradient(row, stops) {
|
|
105
|
+
const colors = stops.map((pos) => rgbToCompressedColor(row[pos].r, row[pos].g, row[pos].b));
|
|
106
|
+
if (colors.every((c) => c === colors[0])) {
|
|
107
|
+
return `linear-gradient(${colors[0]},${colors[0]})`;
|
|
108
|
+
}
|
|
109
|
+
const lastIdx = row.length - 1;
|
|
110
|
+
const parts = [];
|
|
111
|
+
for (let i = 0; i < stops.length; i++) {
|
|
112
|
+
const pos = stops[i];
|
|
113
|
+
const color = colors[i];
|
|
114
|
+
if (i === 0 && pos === 0) {
|
|
115
|
+
parts.push(color);
|
|
116
|
+
}
|
|
117
|
+
else if (i === stops.length - 1 && pos === lastIdx) {
|
|
118
|
+
parts.push(color);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
parts.push(`${color} ${pos}px`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return `linear-gradient(90deg,${parts.join(",")})`;
|
|
125
|
+
}
|
|
126
|
+
// ==========================================
|
|
127
|
+
// Public API
|
|
128
|
+
// ==========================================
|
|
129
|
+
export function buildRowGradient(row, tolerance) {
|
|
130
|
+
if (tolerance === 0) {
|
|
131
|
+
const runs = buildRowRuns(row);
|
|
132
|
+
return {
|
|
133
|
+
gradient: runsToHardGradient(runs),
|
|
134
|
+
isSolid: runs.length === 1,
|
|
135
|
+
solidColor: runs.length === 1 ? runs[0].color : null,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const stops = findOptimalStops(row, tolerance);
|
|
139
|
+
const firstColor = rgbToCompressedColor(row[stops[0]].r, row[stops[0]].g, row[stops[0]].b);
|
|
140
|
+
const allSame = stops.every((s) => rgbToCompressedColor(row[s].r, row[s].g, row[s].b) === firstColor);
|
|
141
|
+
return {
|
|
142
|
+
gradient: stopsToSmoothGradient(row, stops),
|
|
143
|
+
isSolid: allSame,
|
|
144
|
+
solidColor: allSame ? firstColor : null,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 画像読み込みモジュール
|
|
3
|
+
* sharpを使って画像をRGBピクセルマトリクスに変換する
|
|
4
|
+
*/
|
|
5
|
+
import sharp from "sharp";
|
|
6
|
+
export async function readImage(input, maxWidth) {
|
|
7
|
+
let pipeline = sharp(input);
|
|
8
|
+
if (maxWidth) {
|
|
9
|
+
pipeline = pipeline.resize(maxWidth, undefined, {
|
|
10
|
+
fit: "inside",
|
|
11
|
+
withoutEnlargement: true,
|
|
12
|
+
kernel: sharp.kernel.lanczos3,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
const { data, info } = await pipeline.removeAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
16
|
+
const { width, height } = info;
|
|
17
|
+
const pixels = [];
|
|
18
|
+
for (let y = 0; y < height; y++) {
|
|
19
|
+
const row = [];
|
|
20
|
+
for (let x = 0; x < width; x++) {
|
|
21
|
+
const idx = (y * width + x) * 3;
|
|
22
|
+
row.push({
|
|
23
|
+
r: data[idx],
|
|
24
|
+
g: data[idx + 1],
|
|
25
|
+
b: data[idx + 2],
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
pixels.push(row);
|
|
29
|
+
}
|
|
30
|
+
return { width, height, pixels };
|
|
31
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { colorsAreSimilar, rgbToCompressedColor } from "./color-utils.js";
|
|
2
|
+
export { assembleCSS, findDominantColor } from "./css-assembler.js";
|
|
3
|
+
export { buildRowGradient, type RowGradientResult, } from "./gradient-builder.js";
|
|
4
|
+
export { readImage } from "./image-reader.js";
|
|
5
|
+
export type { ColorRun, ConvertOptions, ImageData, RGB } from "./types.js";
|
|
6
|
+
export interface ConvertImageOptions {
|
|
7
|
+
width?: number;
|
|
8
|
+
tolerance?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function convertImageToCSS(input: string | Buffer, options?: ConvertImageOptions): Promise<string>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { colorsAreSimilar, rgbToCompressedColor } from "./color-utils.js";
|
|
2
|
+
export { assembleCSS, findDominantColor } from "./css-assembler.js";
|
|
3
|
+
export { buildRowGradient, } from "./gradient-builder.js";
|
|
4
|
+
export { readImage } from "./image-reader.js";
|
|
5
|
+
import { assembleCSS } from "./css-assembler.js";
|
|
6
|
+
import { readImage } from "./image-reader.js";
|
|
7
|
+
export async function convertImageToCSS(input, options = {}) {
|
|
8
|
+
const { width, tolerance = 0 } = options;
|
|
9
|
+
const imageData = await readImage(input, width);
|
|
10
|
+
return assembleCSS(imageData, tolerance);
|
|
11
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface RGB {
|
|
2
|
+
r: number;
|
|
3
|
+
g: number;
|
|
4
|
+
b: number;
|
|
5
|
+
}
|
|
6
|
+
export interface ImageData {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
pixels: RGB[][];
|
|
10
|
+
}
|
|
11
|
+
export interface ConvertOptions {
|
|
12
|
+
width?: number;
|
|
13
|
+
tolerance?: number;
|
|
14
|
+
output?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ColorRun {
|
|
17
|
+
color: string;
|
|
18
|
+
start: number;
|
|
19
|
+
end: number;
|
|
20
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kongyo2/image-to-pure-css",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Convert images to pure CSS using linear gradients",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"kongyo-css": "./dist/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"lint": "biome check src",
|
|
23
|
+
"lint:fix": "biome check --write src",
|
|
24
|
+
"check": "biome check src && tsc --noEmit",
|
|
25
|
+
"prepublishOnly": "npm run check && npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"css",
|
|
29
|
+
"image",
|
|
30
|
+
"converter",
|
|
31
|
+
"gradient",
|
|
32
|
+
"pure-css"
|
|
33
|
+
],
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/kongyo2/image-to-pure-css.git"
|
|
37
|
+
},
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"sharp": "^0.33.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@biomejs/biome": "2.4.8",
|
|
47
|
+
"@types/node": "^22.0.0",
|
|
48
|
+
"tsx": "^4.19.0",
|
|
49
|
+
"typescript": "^5.7.0"
|
|
50
|
+
}
|
|
51
|
+
}
|