├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs-assets
├── screenshot.jpg
└── thumbnail.jpg
├── package-lock.json
├── package.json
├── public
├── img2css.jpg
└── index.html
└── src
├── app.jsx
└── index.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | insert_final_newline = true
8 | trim_trailing_whitespace = true
9 |
10 | [*.md]
11 | max_line_length = off
12 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | },
6 | extends: ['eslint:recommended', 'plugin:react/recommended'],
7 | parserOptions: {
8 | ecmaFeatures: {
9 | jsx: true,
10 | },
11 | ecmaVersion: 12,
12 | sourceType: 'module',
13 | },
14 | plugins: ['react'],
15 | rules: {},
16 | };
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /dist
5 | .vercel
6 | /node_modules
7 | /.pnp
8 | .pnp.js
9 |
10 | # testing
11 | /coverage
12 |
13 | # production
14 | /build
15 |
16 | # misc
17 | .DS_Store
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "printWidth": 80,
4 | "editor.formatOnSave": true,
5 | "proseWrap": "always",
6 | "tabWidth": 2,
7 | "requireConfig": false,
8 | "useTabs": false,
9 | "trailingComma": "es5",
10 | "bracketSpacing": true,
11 | "jsxBracketSameLine": false,
12 | "semi": true
13 | }
14 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 1.1.0
2 |
3 | - Add base64 output option
4 | - Replace internal components for JBX
5 |
6 | # 1.0.0
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2019, Javier Bórquez
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [img2css](https://javier.xyz/img2css/)
2 |
3 | Convert any image to pure CSS.
4 |
5 | [](https://javier.xyz/img2css/)
6 |
7 | - To use it go to https://javier.xyz/img2css
8 | - Looking for a programmatic way to do this? See https://github.com/javierbyte/canvas-image-utils
9 | - I also made a per-pixel animation experiment, see https://github.com/javierbyte/morphin
10 |
11 | ## How does it work?
12 |
13 | It has two different outputs, pure css shadow matrix [1] and base64 embedded image [2].
14 |
15 | - **Pure CSS**: This output was created by resizing and setting each pixel as a box-shadow of a single pixel div, so no `img` tag or `background-image` is needed. This can result in huge outputs, and the use of this output is not recommended for production unless there is no other option.
16 | - **Base64**: The entire image file is embedded inside the `` tag using base64, so no need external hosting is needed.
17 |
18 | ### Development
19 |
20 | Run development server:
21 |
22 | ```
23 | npm run dev
24 | ```
25 |
26 | Build
27 |
28 | ```
29 | npm run build
30 | ```
31 |
--------------------------------------------------------------------------------
/docs-assets/screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javierbyte/img2css/441c7c968f0a659b48392c8cf7fd61b9f854d329/docs-assets/screenshot.jpg
--------------------------------------------------------------------------------
/docs-assets/thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javierbyte/img2css/441c7c968f0a659b48392c8cf7fd61b9f854d329/docs-assets/thumbnail.jpg
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "img2css",
3 | "version": "1.2.0",
4 | "description": "Convert any image to pure css.",
5 | "scripts": {
6 | "dev": "react-scripts start",
7 | "build": "react-scripts build",
8 | "test": "react-scripts test",
9 | "eject": "react-scripts eject",
10 | "predeploy": "npm run build",
11 | "deploy": "gh-pages -d build"
12 | },
13 | "homepage": "https://javier.xyz/img2css/",
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/javierbyte/img2css.git"
17 | },
18 | "keywords": [
19 | "img2css",
20 | "images",
21 | "css",
22 | "experiments",
23 | "pixelart"
24 | ],
25 | "author": "Javier Bórquez (http://github.com/javierbyte)",
26 | "license": "BSD-3-Clause",
27 | "devDependencies": {
28 | "gh-pages": "^3.2.3"
29 | },
30 | "dependencies": {
31 | "canvas-image-utils": "^2.1.1",
32 | "capsize": "^2.0.0",
33 | "jbx": "^1.7.6",
34 | "lodash": "^4.17.20",
35 | "react": "^18.0.0",
36 | "react-dom": "^18.0.0",
37 | "react-scripts": "^5.0.0",
38 | "styled-components": "^5.3.5",
39 | "tinycolor2": "^1.4.2"
40 | },
41 | "browserslist": {
42 | "production": [
43 | ">0.2%",
44 | "not dead",
45 | "not op_mini all"
46 | ],
47 | "development": [
48 | "last 1 chrome version",
49 | "last 1 firefox version",
50 | "last 1 safari version"
51 | ]
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/public/img2css.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javierbyte/img2css/441c7c968f0a659b48392c8cf7fd61b9f854d329/public/img2css.jpg
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | img2css | Convert any image to pure CSS
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
82 |
83 |
84 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/src/app.jsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useState } from 'react';
2 | import tinycolor from 'tinycolor2';
3 | import _ from 'lodash';
4 |
5 | import {
6 | JBX,
7 | MainHeader,
8 | Text,
9 | Space,
10 | A,
11 | Container,
12 | Dropzone,
13 | Ul,
14 | Li,
15 | Tabs,
16 | Tab,
17 | Inline,
18 | } from 'jbx';
19 |
20 | import Styled from 'styled-components';
21 |
22 | import { imageToRGBMatrix, imageToRawData } from 'canvas-image-utils';
23 |
24 | const Textarea = Styled.textarea({
25 | fontFamily: 'monaco, monospace',
26 | border: 'none',
27 | width: '100%',
28 | height: 256,
29 | fontSize: 13,
30 | lineHeight: 1.309,
31 | padding: '16px 18px',
32 | background: '#ecf0f1',
33 | color: '#34495e',
34 | ':focus': {
35 | outline: 'none',
36 | },
37 | });
38 |
39 | function compressColor(rgb) {
40 | const hex = tinycolor(rgb).toHexString();
41 |
42 | switch (
43 | hex // based on CSS3 supported color names http://www.w3.org/TR/css3-color/
44 | ) {
45 | case '#c0c0c0':
46 | return 'silver';
47 | case '#808080':
48 | return 'gray';
49 | case '#800000':
50 | return 'maroon';
51 | case '#ff0000':
52 | return 'red';
53 | case '#800080':
54 | return 'purple';
55 | case '#008000':
56 | return 'green';
57 | case '#808000':
58 | return 'olive';
59 | case '#000080':
60 | return 'navy';
61 | case '#008080':
62 | return 'teal';
63 | }
64 | return hex[1] === hex[2] && hex[3] === hex[4] && hex[5] === hex[6]
65 | ? '#' + hex[1] + hex[3] + hex[5]
66 | : hex;
67 | }
68 |
69 | function App() {
70 | const [outputType, outputTypeSet] = useState('SHADOW');
71 | const [originalSize, originalSizeSet] = useState(0);
72 | const [base64Data, base64DataSet] = useState('');
73 | const [rgbMatrix, rgbMatrixSet] = useState(null);
74 | const [loadingImage, loadingImageSet] = useState(false);
75 |
76 | function onFileSelected(event) {
77 | event.stopPropagation();
78 | event.preventDefault();
79 |
80 | const dt = event.dataTransfer;
81 | const files = dt ? dt.files : event.target.files;
82 | const file = files[0];
83 |
84 | originalSizeSet(file.size);
85 |
86 | const fr = new window.FileReader();
87 |
88 | loadingImageSet(true);
89 |
90 | fr.onload = async (data) => {
91 | const base64src = data.currentTarget.result;
92 | const dataMatrix = await imageToRGBMatrix(base64src, { size: 200 });
93 | const canvasRawData = await imageToRawData(base64src, {
94 | size: 1080,
95 | crop: false,
96 | });
97 |
98 | base64DataSet(canvasRawData.ctx.canvas.toDataURL('image/jpeg', 0.75));
99 | rgbMatrixSet(dataMatrix);
100 | loadingImageSet(false);
101 | };
102 | fr.readAsDataURL(file);
103 | }
104 |
105 | function onDragOver(event) {
106 | event.stopPropagation();
107 | event.preventDefault();
108 | }
109 |
110 | let scale = 1;
111 |
112 | const masterShadow = _.map(rgbMatrix, (row, rowIndexSrc) => {
113 | return _.map(row, (col, colIndexSrc) => {
114 | const i = colIndexSrc * scale;
115 | const j = rowIndexSrc * scale;
116 |
117 | const color = compressColor(`rgb(${col.r},${col.g},${col.b})`);
118 |
119 | const scaleCompensation = scale !== 1 ? ` 0 ${scale / 2}px` : ``;
120 |
121 | return `${color} ${j ? j + 'px' : 0} ${
122 | i ? i + 'px' : 0
123 | }${scaleCompensation}`;
124 | }).join(',');
125 | }).join(',');
126 |
127 | const handleFocus = (event) => {
128 | event.preventDefault();
129 | event.stopPropagation();
130 | event.target.select();
131 | };
132 |
133 | return (
134 |
135 |
136 | img2css
137 |
138 |
139 |
140 | This is a tool that can convert any image into a pure css image.
141 |
142 |
143 |
144 |
149 | {loadingImage ? (
150 | Processing...
151 | ) : (
152 |
153 | Drop an image here
154 | or click to select
155 |
156 | )}
157 |
164 |
165 |
166 |
167 |
168 | I also made a per-pixel animation experiment using the box-shadow idea,
169 | see morphin.
170 |
171 |
172 | {rgbMatrix && (
173 |
174 |
175 |
176 |
177 | {
181 | outputTypeSet('SHADOW');
182 | }}
183 | >
184 | {'Pure CSS'}
185 |
186 | {
190 | outputTypeSet('BASE64');
191 | }}
192 | >
193 | {'Base64'}
194 |
195 |
196 |
197 |
198 |
199 | {outputType === 'BASE64' && (
200 |
201 |
202 | The result (base64).{' '}
203 | {
204 | 'This is your image tag a base64 output. The entire image file is embedded inside the `` tag using base64, so no need external hosting is needed.'
205 | }
206 |
207 |
208 |
209 |
213 |
214 |
215 |
216 |
230 | )}
231 |
232 | {outputType === 'SHADOW' && (
233 |
234 |
235 | The result (pure CSS). This is your pure CSS
236 | (and single div) image, enjoy! This output was created by
237 | resizing and setting each pixel as a box-shadow of a single
238 | pixel div, so no `img` tag or `background-image` is needed. This
239 | can result in huge outputs, and the use of this output is not
240 | recommended for production unless there is no other option.
241 |
242 |
243 |
252 |
253 |
271 | )}
272 |
273 | )}
274 |
275 |
276 |
277 | More unrelated experiments:
278 |
279 |
280 |
281 |
282 | PINTR, create single line
283 | SVG illustrations from your pictures.
284 |
285 |
286 | Sombras.app, play with 3D and
287 | shadows.
288 |
289 |
290 | Cohesive Colors,
291 | create more cohesive color palettes.
292 |
293 |
294 | Visual Center, find
295 | the visual center in your images / logos.
296 |