Giter Club home page Giter Club logo

jspaint's Introduction

A pixel-perfect web-based MS Paint remake and more... Try it out!

JS Paint recreates every tool and menu of MS Paint, and even little-known features, to a high degree of fidelity.

It supports themes, additional file types, and accessibility features like Eye Gaze Mode and Speech Recognition.

Screenshot

Ah yes, good old Paint. Not the one with the ribbons or the new skeuomorphic one with the interface that can take up nearly half the screen. (And not the even newer Paint 3D.)

Windows 95, 98, and XP were the golden years of Paint. You had a tool box and a color box, a foreground color and a background color, and that was all you needed.

Things were simple.

But we want to undo more than three actions. We want to edit transparent images. We can't just keep using the old Paint.

So that's why I'm making JS Paint. I want to bring good old Paint into the modern era.

Current improvements include:

  • Open source (MIT licensed)
  • Cross-platform
  • Mobile friendly
    • Touch support: use two fingers to pan the view, and pinch to zoom
    • Click/tap the selected colors area to swap the foreground and background colors
    • View > Fullscreen to toggle fullscreen mode, nice for small screens
  • Web features
    • File > Load From URL... to open an image from the Web.
    • File > Upload to Imgur to upload the current image to Imgur.
    • Paste supports loading from URLs.
    • You can create links that will open an image from the Web in JS Paint. For example, this link will start with an isometric grid as a template: https://jspaint.app/#load:https://i.imgur.com/zJMrWwb.png
    • Rudimentary multi-user collaboration support. Start up a session at jspaint.app/#session:multi-user-test and send the link to your friends! It isn't seamless; actions by other users interrupt what you're doing, and visa versa. Sessions are not private, and you may lose your work at any time. If you want better collaboration support, follow the development of Mopaint.
  • Extras > Themes to change the look of the app. Dark mode included.
  • Eye Gaze Mode, for use with an eye tracker, head tracker, or other coarse input device, accessible from Extras > Eye Gaze Mode. With just a webcam, you can try it out with Enable Viacam (head tracker) or GazePointer (eye tracker).
  • Speech Recognition Mode. Using your voice you can select tools and colors, pan the view ("scroll down and to the left", or "go southwest", etc.), explore the menus (but you can activate any menu item without opening the menus first), interact with windows (including scrolling the history view with "scroll up"/"scroll down" etc.), dictate text with the Text tool, and even tell the application to sketch things (for instance, "draw a house")
  • Create an animated GIF from the current document history. Accessible from the Extras menu or with Ctrl+Shift+G. It's pretty nifty, you should try it out! You might want to limit the size of the image though.
  • Load and save many different palette formats with Colors > Get Colors and Colors > Save Colors. (I made a library for this: AnyPalette.js.)
    • You can also drag and drop palette files into the app to load.

Editing Features:

  • Use Alt+Mousewheel to zoom in and out
  • Edit transparent images! To create a transparent image, go to Image > Attributes... and select Transparent, then OK, and then Image > Clear Image or use the Eraser tool. Images with any translucent pixels will open in Transparent mode.
  • You can crop the image by making a selection while holding Ctrl
  • Keyboard shortcuts for rotation: Ctrl+. and Ctrl+, (< and >)
  • Rotate by any arbitrary angle in Image > Flip/Rotate
  • In Image > Stretch/Skew, you can stretch more than 500% at once
  • Zoom to an arbitrary scale in View > Zoom > Custom...
  • Zoom to fit the canvas within the window with View > Zoom > Zoom To Window
  • Non-contiguous fill: Replace a color in the entire image by holding Shift when using the fill tool

Miscellaneous Improvements:

  • Vertical Color Box mode, accessible from Extras > Vertical Color Box
  • You can use the Text tool at any zoom level (and it previews the exact pixels that will end up on the canvas).
  • Spellcheck is available in the textbox if your browser supports it.
  • Resize handles are easier to grab than in Windows 10's Paint.
  • Omits some Thumbnail view bugs, like the selection showing in the wrong place.
  • Unlimited undos/redos (as opposed to a measly 3 in Windows XP, or a measly 50 in Windows 7)
  • Undo history is nonlinear, which means if you undo and do something other than redo, the redos aren't discarded. Instead, a new branch is created in the history tree. Jump to any point in history with Edit > History or Ctrl+Shift+Y
  • Automatically keeps a backup of your image. Only one backup per image tho, which doesn't give you a lot of safety. Remember to save with File > Save or Ctrl+S! Manage backups with File > Manage Storage.

JS Paint drawing of JS Paint on a phone

Limitations:

A few things with the tools aren't done yet. See TODO.md

Full clipboard support in the web app requires a browser supporting the Async Clipboard API w/ Images, namely Chrome 76+ at the time of writing.

In other browsers you can still copy with Ctrl+C, cut with Ctrl+X, and paste with Ctrl+V, but data copied from JS Paint can only be pasted into other instances of JS Paint. External images can be pasted in.

Supported File Formats

Image Formats

⚠️ Saving as JPEG will introduce artifacts that cause problems when using the Fill tool or transparent selections.

⚠️ Saving in some formats will reduce the number of colors in the image.

πŸ’‘ Unlike in MS Paint, you can use Edit > Undo to revert color or quality reduction from saving. This doesn't undo saving the file, but allows you to then save in a different format with higher quality, using File > Save As.

πŸ’‘ Saving as PNG is recommended as it gives small file sizes while retaining full quality.

File Extension Name Read Write Read Palette Write Palette
.png PNG βœ… βœ… πŸ”œ
.bmp, .dib Monochrome Bitmap βœ… βœ… πŸ”œ βœ…
.bmp, .dib 16 Color Bitmap βœ… βœ… πŸ”œ βœ…
.bmp, .dib 256 Color Bitmap βœ… βœ… πŸ”œ βœ…
.bmp, .dib 24-bit Bitmap βœ… βœ… N/A N/A
.tif, .tiff, .dng, .cr2, .nef TIFF (loads first page) βœ… βœ…
.pdf PDF (loads first page) βœ…
.webp WebP 🌐 🌐
.gif GIF 🌐 🌐
.jpeg, .jpg JPEG 🌐 🌐 N/A N/A
.svg SVG (only default size) 🌐
.ico ICO (only default size) 🌐

Capabilities marked with 🌐 are currently left up to the browser to support or not. If "Write" is marked with 🌐, the format will appear in the file type dropdown but may not work when you try to save. For opening files, see Wikipedia's browser image format support table for more information.

Capabilities marked with πŸ”œ may be coming soon, and N/A means not applicable.

"Read Palette" refers to loading the colors into the Colors box automatically (from an indexed color image), and "Write Palette" refers to writing an indexed color image.

Color Palette Formats

With Colors > Save Colors and Colors > Get Colors you can save and load colors in many different formats, for compatibility with a wide range of programs.

If you want to add extensive palette support to another application, I've made this functionality available as a library: AnyPalette.js

File Extension Name Programs Read Write
.pal RIFF Palette MS Paint for Windows 95 and Windows NT 4.0 βœ… βœ…
.gpl GIMP Palette Gimp, Inkscape, Krita, KolourPaint, Scribus, CinePaint, MyPaint βœ… βœ…
.aco Adobe Color Swatch Adobe Photoshop βœ… βœ…
.ase Adobe Swatch Exchange Adobe Photoshop, InDesign, and Illustrator βœ… βœ…
.txt Paint.NET Palette Paint.NET βœ… βœ…
.act Adobe Color Table Adobe Photoshop and Illustrator βœ… βœ…
.pal, .psppalette Paint Shop Pro Palette Paint Shop Pro (Jasc Software / Corel) βœ… βœ…
.hpl Homesite Palette Allaire Homesite / Macromedia ColdFusion βœ… βœ…
.cs ColorSchemer ColorSchemer Studio βœ…
.pal StarCraft Palette StarCraft βœ… βœ…
.wpe StarCraft Terrain Palette StarCraft βœ… βœ…
.sketchpalette Sketch Palette Sketch βœ… βœ…
.spl Skencil Palette Skencil (formerly called Sketch) βœ… βœ…
.soc StarOffice Colors StarOffice, OpenOffice, LibreOffice βœ… βœ…
.colors KolourPaint Color Collection KolourPaint βœ… βœ…
.colors Plasma Desktop Color Scheme KDE Plasma Desktop βœ…
.theme Windows Theme Windows Desktop βœ…
.themepack Windows Theme Windows Desktop βœ…
.css, .scss, .styl Cascading StyleSheets Web browsers / web pages βœ… βœ…
.html, .svg, .js any text files with CSS colors Web browsers / web pages βœ…

Did you know?

  • There's a black and white mode with patterns instead of colors in the palette, which you can get to from Image > Attributes...

  • You can drag the color box and tool box around if you grab them by the right place. You can even drag them out into little windows. You can dock the windows back to the side by double-clicking on their title bars.

  • In addition to the left-click foreground color and the right-click background color, there's a third color you can access by holding Ctrl while you draw. It starts out with no color so you'll need to hold Ctrl and select a color first. The fancy thing about this color slot is you can press and release Ctrl to switch colors while drawing.

  • You can apply image transformations like Flip/Rotate, Stretch/Skew or Invert (in the Image menu) either to the whole image or to a selection. Try scribbling with the Free-Form Select tool and then doing Image > Invert

  • These Tips and Tricks from a tutorial for MS Paint also work in JS Paint:

    • Brush Scaling (+ & - on the number pad to adjust brush size)
    • "Custom Brushes" (hold Shift and drag the selection to smear it)
    • The 'Stamp' "Tool" (hold Shift and click the selection to stamp it)
    • Image Scaling (+ & - on the number pad to scale the selection by factors of 2)
    • Color Replacement (right mouse button with Eraser to selectively replace the foreground color with the background color)
    • The Grid (Ctrl+G & Zoom to 4x+)
    • Quick Undo (Pressing a second mouse button cancels the action you were performing. I also made it redoable, in case you do it by accident!)
    • Scroll Wheel Bug (Hmm, let's maybe not recreate this?)

Desktop App

PWA

JS Paint can be installed as a Progressive Web App (PWA), although it doesn't work offline yet. Look for the install prompt in the address bar.

PWA features:

  • No address bar; middle-ground between web and native
  • Cross-platform (macOS, Windows, Linux, Android, iOS)
  • Basic file integration:
    • File > Open
    • File > Save downloads the file after asking for a filename and format
    • Drag and drop files onto the window to open them

Missing features:

  • Directly saving to files is implemented but not enabled currently.I was concerned about data loss for two reasons: 1. the change in behavior of File > Save / Ctrl+S from effectively acting as Save As to overwriting files directly, although I made a warning dialog for this, with a don't show again option; 2. there was a bad bug with saved files ending up completely empty (zero bytes), which I don't know if was a bug in my code or in Chrome.
  • Offline support is not implemented.I've taken a few stabs at this, and I'm not the only one, but there are some huge caveats, such as the development server not being able to live-reload without disabling the service worker.

Electron

I've also built it into a desktop app with Electron and Electron Forge. You can download it from the releases page.

JS Paint running as a desktop app on macOS

Electron app features:

  • Native-like experience (runs in a window with no address bar)
  • Cross-platform (macOS, Windows, Linux)
  • Clipboard support
  • Files can be opened in various ways:
    • File > Open
    • Drag and drop onto window
    • Drag and drop onto dock icon on macOS
    • Drag and drop onto desktop shortcut
    • Right Click > Open With in file manager (macOS and Linux)
      • On Windows, you can manually paste the path to the executable into the Open With dialog, which you can find by right clicking the app in the taskbar, then right clicking the app's name, and selecting Properties. In the Shortcut tab, the Target field is the path to the executable. Once you open the app in this way, the app will show up in the Open With list, and if you select "Always", it will become the default app for that file type.
    • Command line: type jspaint path/to/file.png in the terminal
  • File > Save will save directly to the file
  • File > Set As Wallpaper (Tiled) and File > Set As Wallpaper (Centered)
  • On macOS, an icon representing the currently open file is shown in the titlebar. You can drag this icon into other applications, for example to include the image you're editing in an email. The icon is dimmed while there are unsaved changes.
Electron app limitations
  • Basics:
    • Execution is blocked by default on Mac and Windows
      • On macOS you need to Ctrl+click the file and then say Open
      • On Windows, you need to say "More info" and then "Run" (or "Run anyway"?)
      • I would need to pay a fee for code signing to avoid this. It's basically security by extortion.
    • There are no automatic updates. Apparently I would need to pay a fee for code signing to get this free service.
      • That said, Help > About Paint can tell you if JS Paint is out of date, at least in terms of news updates.
    • Electron is out of date. It may, for instance, contain image decoding vulnerabilities that have since been fixed. However, I've taken precautions to sandbox the app and restrict write access to a list of files explicitly opened in the app, the list being controlled by the main process, separate from the renderer process which would handle image decoding.
    • Only a single editor window can be opened at once.
    • The File menu's recent files list is not implemented, nor are OS-specific jump menus.
  • Minor details:
    • A very confusing message is shown if you edit a document before clicking an Open link in the Manage Storage dialog.
    • WebGL error messages tell you to refresh without offering a way to reload; also, calling the app a web page feels unpolished
    • The File > Open dialog does not have an All Files (*.*) option, and the list of file types supported is not exhaustive; for example, AVIF images can be loaded but only by drag and drop
    • Drag and drop shows two "Save changes to X?" dialogs on top of each other?
  • I'm not sure if all of these are still issues, need to retest them:

Development Setup

Clone the repo.

Install Node.js if you don't have it, then open up a command prompt / terminal in the project directory.

Testing

Run npm run lint to check for code problems.

Run npm run spellcheck to check for spelling errors. This may be included in the linting script in the future.

Run npm run typecheck to check for type errors. This may be included in the linting script in the future. Not much of the codebase is typechecked yet.

Run npm test to run browser-based tests with Cypress. (It's slow to start up and run tests, unfortunately.)

Run npm run accept to accept any visual changes. This unfortunately re-runs all the tests, rather than accepting results of the previous test, so you could end up with different results than the previous test. If you use GitHub Desktop, you can view diffs of images, in four different modes.

To open the Cypress UI, first run npm run test:start-server, then concurrently npm run cy:open

Tests are also run in continuous integration with Travis CI.

After you've installed dependencies with npm i, use npm run dev to start a live-reloading server.

Make sure any layout-important styles go in layout.css. When updating layout.css, a right-to-left version of the stylesheet is generated, using RTLCSS.
You should test the RTL layout by changing the language to Arabic or Hebrew. Go to Extras > Language > Ψ§Ω„ΨΉΨ±Ψ¨ΩŠΨ© or Χ’Χ‘Χ¨Χ™Χͺ.
See Control Directives for how to control the RTL layout.

There is a VS Code launch task for attaching to Chrome for debugging. See .vscode/launch.json for usage instructions.

Desktop App (Electron)

  • Install dependencies with npm i
  • Start the electron app with npm run electron:start

electron-debug is included, so you can use F5/Ctrl+R to reload and F12/Ctrl+Shift+I to open the devtools.

You can build for production with npm run electron:make

There is a VS Code launch task for debugging the Electron main process. For the renderer process, you can use the embedded Chrome DevTools.

Deployment

JS Paint can be deployed using a regular web server.

Nothing needs to be compiled.

CORS proxy

Optionally, you can set up a CORS Anywhere server, for loading images from the web, if you paste a URL into JS Paint, or use the #load:<URL> feature with images that are not on the same domain.

By default it will use a CORS Anywhere instance set up to work with jspaint.app.

It is hosted for free on Heroku, and you can set up your own instance and configure it to work with your own domain.

You'll have to find and replace https://jspaint-cors-proxy.herokuapp.com with your own instance URL.

Multiplayer Support

Multiplayer support currently relies on Firebase, which is not open source software.

You could create a Firebase Realtime Database instance and edit JS Paint's sessions.js to point to it, replacing the config passed to initializeApp with the config from the Firebase Console when you set up a Web App.

But the multiplayer mode is very shoddy so far. It should be replaced with something open source, more secure, more efficient, and more robust.

Embed in your website

Simple

Add this to your HTML:

<iframe src="https://jspaint.app" width="100%" height="100%"></iframe>

Start with an image

You can have it load an image from a URL by adding #load:<URL> to the URL.

<iframe src="https://jspaint.app#load:https://jspaint.app/favicon.ico" width="100%" height="100%"></iframe>

Advanced

If you want to control JS Paint, how it saves/loads files, or access the canvas directly, there is an unstable API.

First you need to clone the repo, so you can point an iframe to your local copy.

The local copy of JS Paint has to be hosted on the same web server as the containing page, or more specifically, it has to share the same origin.

Having a local copy also means things won't break any time the API changes.

If JS Paint is cloned to a folder called jspaint, which lives in the same folder as the page you want to embed it in, you can use this:

<iframe src="jspaint/index.html" id="jspaint-iframe" width="100%" height="100%"></iframe>

If it lives somewhere else, you may need to add ../ to the start of the path, to go up a level. For example, src="../../apps/jspaint/index.html". You can also use an absolute URL, like src="https://example.com/cool-apps/jspaint/index.html".

Changing how files are saved/loaded

You can override the file saving and opening dialogs with JS Paint's systemHooks API.

<script>
var iframe = document.getElementById('jspaint-iframe');
var jspaint = iframe.contentWindow;
// Wait for systemHooks object to exist (the iframe needs to load)
waitUntil(()=> jspaint.systemHooks, 500, ()=> {
	// Hook in
	jspaint.systemHooks.showSaveFileDialog = async ({ formats, defaultFileName, defaultPath, defaultFileFormatID, getBlob, savedCallbackUnreliable, dialogTitle }) => { ... };
	jspaint.systemHooks.showOpenFileDialog = async ({ formats }) => { ... };
	jspaint.systemHooks.writeBlobToHandle = async (save_file_handle, blob) => { ... };
	jspaint.systemHooks.readBlobFromHandle = async (file_handle) => { ... };
});
// General function to wait for a condition to be met, checking at regular intervals
function waitUntil(test, interval, callback) {
	if (test()) {
		callback();
	} else {
		setTimeout(waitUntil, interval, test, interval, callback);
	}
}
</script>

A Blob represents the contents of a file in memory.

A file handle is anything that can identify a file. You get to own this concept, and define how to identify files. It could be anything from an index into an array, to a Dropbox file ID, to an IPFS URL, to a file path. It can be any type, or maybe it needs to be a string, I forget.

Once you have a concept of a file handle, you can implement file pickers using the system hooks, and functions to read and write files.

Command Hooks Used
File > Save As systemHooks.showSaveFileDialog, then when a file is picked, systemHooks.writeBlobToHandle
File > Open systemHooks.showOpenFileDialog, then when a file is picked, systemHooks.readBlobFromHandle
File > Save systemHooks.writeBlobToHandle (or same as File > Save As if there's no file open yet)
Edit > Copy To systemHooks.showSaveFileDialog, then when a file is picked, systemHooks.writeBlobToHandle
Edit > Paste From systemHooks.showOpenFileDialog, then when a file is picked, systemHooks.readBlobFromHandle
File > Set As Wallpaper (Tiled) systemHooks.setWallpaperTiled if defined, else systemHooks.setWallpaperCentered if defined, else same as File > Save As
File > Set As Wallpaper (Centered) systemHooks.setWallpaperCentered if defined, else same as File > Save As
Extras > Render History As GIF Same as File > Save As
Colors > Save Colors Same as File > Save As
Colors > Get Colors Same as File > Open

Loading a file initially

To start the app with a file loaded for editing, wait for the app to load, then call systemHooks.readBlobFromHandle with a file handle, and tell the app to load that file blob.

const file_handle = "initial-file-to-load";
systemHooks.readBlobFromHandle(file_handle).then(file => {
	if (file) {
		contentWindow.open_from_file(file, file_handle);
	}
}, (error) => {
	// Note: in some cases, this handler may not be called, and instead an error message is shown by readBlobFromHandle directly.
	contentWindow.show_error_message(`Failed to open file ${file_handle}`, error);
});

This is clumsy, and in the future there may be a query string parameter to load an initial file by its handle. (Note to self: it will need to wait for your system hooks to be registered, somehow.)

There's already a query string parameter to load from a URL:

<iframe src="https://jspaint.app?load:SOME_URL_HERE"></iframe>

But this won't set up the file handle for saving.

Integrating Set as Wallpaper

You can define two functions to set the wallpaper, which will be used by File > Set As Wallpaper (Tiled) and File > Set As Wallpaper (Centered).

If you define only systemHooks.setWallpaperCentered, JS Paint will attempt to guess your screen's dimensions and tile the image, applying it by calling your systemHooks.setWallpaperCentered function.

If you don't specify systemHooks.setWallpaperCentered, JS Paint will default to saving a file (<original file name> wallpaper.png) using systemHooks.showSaveFileDialog and systemHooks.writeBlobToHandle.

Here's a full example supporting a persistent custom wallpaper as a background on the containing page:

const wallpaper = document.querySelector('body'); // or some other element

jspaint.systemHooks.setWallpaperCentered = (canvas) => {
	canvas.toBlob((blob) => {
		setDesktopWallpaper(blob, "no-repeat", true);
	});
};
jspaint.systemHooks.setWallpaperTiled = (canvas) => {
	canvas.toBlob((blob) => {
		setDesktopWallpaper(blob, "repeat", true);
	});
};

function setDesktopWallpaper(file, repeat, saveToLocalStorage) {
	const blob_url = URL.createObjectURL(file);
	wallpaper.style.backgroundImage = `url(${blob_url})`;
	wallpaper.style.backgroundRepeat = repeat;
	wallpaper.style.backgroundPosition = "center";
	wallpaper.style.backgroundSize = "auto";
	if (saveToLocalStorage) {
		const fileReader = new FileReader();
		fileReader.onload = () => {
			localStorage.setItem("wallpaper-data-url", fileReader.result);
			localStorage.setItem("wallpaper-repeat", repeat);
		};
		fileReader.onerror = () => {
			console.error("Error reading file (for setting wallpaper)", file);
		};
		fileReader.readAsDataURL(file);
	}
}

// Initialize the wallpaper from localStorage, if it exists
try {
	const wallpaper_data_url = localStorage.getItem("wallpaper-data-url");
	const wallpaper_repeat = localStorage.getItem("wallpaper-repeat");
	if (wallpaper_data_url) {
		fetch(wallpaper_data_url).then(response => response.blob()).then(file => {
			setDesktopWallpaper(file, wallpaper_repeat, false);
		});
	}
} catch (error) {
	console.error(error);
}

It's a little bit recursive, sorry; it could probably be done simpler. Like by just using data URLs. (Actually, I think I wanted to use blob URLs just so that it doesn't bloat the DOM inspector with a super long URL. Which is really a devtools UX bug. Maybe they've improved this?)

Specifying the canvas size

You can load a file that has the desired dimensions. There's no special API for this at the moment.

See Loading a file initially.

Specifying the theme

You could change the theme programmatically:

var iframe = document.getElementById('jspaint-iframe');
var jspaint = iframe.contentWindow;
jspaint.set_theme("modern.css");

but this will break the user preference.

The Extras > Themes menu will still work, but the preference won't persist when reloading the page.

In the future there may be a query string parameter to specify the default theme. You could also fork jspaint to change the default theme.

Specifying the language

Similar to the theme, you can try to change the language programmatically:

var iframe = document.getElementById('jspaint-iframe');
var jspaint = iframe.contentWindow;
jspaint.set_language("ar");

but this will actually ask the user to reload the application to change languages.

The Extras > Language menu will still work, but the user will be bothered to change the language every time they reload the page.

In the future there may be a query string parameter to specify the default language. You could also fork jspaint to change the default language.

Adding custom menus

Not supported yet. You could fork jspaint and add your own menus.

Accessing the canvas directly

With access to the canvas, you can implement a live preview of your drawing, for example updating a texture in a game engine in realtime.

var iframe = document.getElementById('jspaint-iframe');
// contentDocument here refers to the webpage loaded in the iframe, not the image document loaded in jspaint.
// We're just reaching inside the iframe to get the canvas.
var canvas = iframe.contentDocument.querySelector(".main-canvas");

It's recommended not to use this for loading a document, as it won't change the document title, or reset undo/redo history, among other things. Instead use open_from_file.

Performing custom actions

If you want to make buttons or other UI to do things to the document, you should (probably) make it undoable. It's very easy, just wrap your action in a call to undoable.

var iframe = document.getElementById('jspaint-iframe');
var jspaint = iframe.contentWindow;
var icon = new Image();
icon.src = "some-folder/some-image-15x11-pixels.png";
jspaint.undoable({
	name: "Seam Carve",
	icon: icon, // optional
}, function() {
	// do something to the canvas
});

Define this function to override the default save dialog. This is used both for saving images, as well as palette files, and animations.

Arguments:

  • formats: an array of objects representing types of files, with the following properties:
    • formatID: a string that uniquely identifies the format (may be the same as mimeType)
    • mimeType: the file format's designated media type, e.g. "image/png"
    • name: the file format's name, e.g. "WebP"
    • nameWithExtensions: the file format's name followed by a list of extensions, e.g. "TIFF (*.tif;*.tiff)"
    • extensions: an array of file extensions, excluding the dot, with the preferred extension first, e.g. ["bmp", "dib"]
  • defaultFileName: a suggested file name, e.g. "Untitled.png" or the name of an open document.
  • defaultPath (optional): a file handle for a document that was opened, so you can save to the same folder easily. Misnomer: this may not be a path, it depends on how you define file handles.
  • defaultFileFormatID: the formatID of a file format to select by default.
  • async function getBlob(formatID): a function you call to get a file in one of the supported formats. It takes a formatID and returns a Promise that resolves with a Blob representing the file contents to save.
  • function savedCallbackUnreliable({ newFileName, newFileFormatID, newFileHandle, newBlob }): a function you call when the user has saved the file. The newBlob should come from getBlob(newFileFormatID).
  • dialogTitle: a title for the save dialog.

Note the inversion of control here: JS Paint calls your systemHooks.showSaveFileDialog function, and then you call JS Paint's getBlob function. Once getBlob resolves, you can call the savedCallbackUnreliable function which is defined by JS Paint. (Hopefully I can clarify this in the future.)

Also note that this function is responsible for saving the file, not just picking a save location. You may reuse your systemHooks.writeBlobToHandle function if it's helpful.

Define this function to override the default open dialog. This is used for opening images and palettes.

Arguments:

  • formats: same as systemHooks.showSaveFileDialog

Note that this function is responsible for loading the contents of the file, not just picking a file. You may reuse your systemHooks.readBlobFromHandle function if it's helpful.

Define this function to tell JS Paint how to save a file.

Arguments:

  • fileHandle: a file handle, as defined by your system, representing the file to write to.
  • blob: a Blob representing the file contents to save.

Returns:

  • Promise that resolves with true if the file was definitely saved successfully, false if an error occurred or the user canceled, or undefined if it is not known whether the file was saved successfully, as is the case with file downloading with <a href="..." download="...">. The promise should not reject; errors should be handled by showing an error message and returning false.

Define this function to tell JS Paint how to load a file.

Arguments:

  • fileHandle: a file handle, as defined by your system, representing the file to read from.

Define this function to tell JS Paint how to set the wallpaper. See Integrating Set as Wallpaper for an example.

Arguments:

  • canvas: a HTMLCanvasElement with the image to set as the wallpaper.

Define this function to tell JS Paint how to set the wallpaper. See Integrating Set as Wallpaper for an example.

Arguments:

  • canvas: a HTMLCanvasElement with the image to set as the wallpaper.

Use this to make an action undoable.

This function takes a snapshot of the canvas, and some other state, and then calls the actionFunction function. It creates an entry in the history so it can be undone.

Arguments:

  • name: a name for the action, e.g. "Brush" or "Rotate Image 270Β°"
  • icon (optional): an Image to display in the History window. It is recommended to be 15x11 pixels.
  • actionFunction: a function that takes no arguments, and modifies the canvas.

Use this to show an error message dialog box, optionally with expandable error details.

Arguments:

  • message: plain text to show in the dialog box.
  • error (optional): an Error object to show in the dialog box, collapsed by default in a "Details" expandable section.

Use this to load a file into the app.

Arguments:

  • blob: a Blob object representing the file to load.
  • source_file_handle: a corresponding file handle for the file, as defined by your system.

Sorry for the quirky API. The API is new, and parts of it have not been designed at all. This was just a hack that I came to depend on, reaching into the internals of JS Paint to load a file. I decided to document it as the first version of the API, since I'll want a changelog when upgrading my usage of it anyways.

Use this to change the look of the application.

Arguments:

  • theme_file_name: the name of the theme file to load, one of:
    • "classic.css": the Windows98 theme.
    • "dark.css": the Dark theme.
    • "modern.css": the Modern theme.
    • "winter.css": the festive Winter theme.
    • "occult.css": a Satanic theme.

You can kind of use this to change the language of the application. But actually it will show a prompt to the user to change the language, because the application needs to reload to apply the change. And if that dialog isn't in the right language, well, they'll probably be confused.

Arguments:

  • language_code: the language code to use, e.g. "en" for English, "zh" for Traditional Chinese, "zh-simplified" for Simplified Chinese, etc.

Changelog

The API will change a lot, but changes will be documented in the Changelog.

Not just a history of changes, but a migration/upgrading guide.

For general project news, click Extras > Project News in the app.

License

JS Paint is free and open source software, licensed under the permissive MIT license.

License GitHub Repo stars GitHub forks

jspaint's People

Contributors

1j01 avatar borbware avatar christianliebel avatar fabijanc avatar lokothodida avatar mikewang000000 avatar milksteakjellybeans avatar mpierre9 avatar mwoodsy avatar ryanjenkins avatar wuweiweiwu avatar yanrishatum avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jspaint's Issues

Selection not saved with session

The selection isn't saved along with the session, so if you have a selection and you refresh or close/reopen the app, a chunk of your image will be missing (irreversibly).

This is compounded by the fact that you don't even need to move the selection for it to cut the selection out of the image. This is not how it works in mspaint, and not how it should work. See #28

It would also be good to save some other data with the session, like the selected tool, tool options, the viewport, the palette, etc.

Efficient undo history

JS Paint allows way more undos than old versions of MS Paint (3 in Win95/98, and 50 in Win7),
but it's way less efficient about it. It currently just naively stores the entire image for every undo state.

It should use rectangle-bounded operations.
This is partially implemented in Mopaint.

Add LICENSE file to the project

To answer standard questions: code contributions, embedding elsewhere, license of the images created by the app, derivative works, license of the libraries used, etc.

As an aside, impressive work you have here!

Menu interaction on mobile

Tapping a menu button other than the one with the menu currently open, will open (on pointerdown) but immediately close the menu (on pointerup), instead of leaving the menu open.
At least on chrome 61 for Android.

Also I can't switch between menus by gliding over the menu buttons, which I think worked previously.

This is an area where the behavior and code behind it is fairly complicated, so I really need some tests for this, ideally automated, but a list of manual testing steps and expected results would be good too. (I've tried to set up automated tests for menu interaction but it was completely unreliable in simulating events. I'll need to find a better framework.)

Double-tap on the touchscreen issue

Please don't hate me for this, I'm aware that it's difficult to detect touchscreen issues on laptops, and especially difficult to fix them without a touchscreen.

So, in Paint, once one presses the screen with two fingers and moves both of the fingers simultaneously, you get this:

capture

In jspaint... well, it depends on the browser. In Firefox, you get this:

capture2

In this case, paintjs automatically drew the line between my two fingers, and drew more lines as I moved the fingers.

If you do the same action in jspaint in a Webkit-based browser, nothing gets drawn.

Note that in all of the examples, a Pencil tool was selected.

Selection not included in saved image

This is really bad especially when pasting screenshots to save. You have to hit enter before saving or else you'll end up saving nothing! And you probably won't even notice, until it's too late!

It's supposed to finalize the selection when you save.

Fix resizing the canvas or the selection when magnified

I need to refactor $Handles & OnCanvasObject. Currently $Handles doesn't really know about the object it's supposed to affect. It uses a callback with a delta position and absolute width/height... It listens for an event to know when the element of the object it's supposed to affect is replaced... and most importantly, it gets the dimensions of the object from getBoundingClientRect. Simply dividing by the magnification level "helps", but it has other issues because it doesn't have a proper source of truth for the dimensions.

Shapes styles doesn't work

When drawing a shape there is ability to select shape style (transparent with borders, filled with borders, borderless filled). This work only with rectangle - other shapes uses only 1 style no matter what I select.

owncloud app

create an owncloud-app :
https://apps.owncloud.com/
https://apps.nextcloud.com/

Owncloud it is a Possibility for Hosting the own Cloud in the basement
via dyndns or as own provider in the own house or something for save the data's by
self and not unfamiliar/foreign Server, where the possibility exist to the
own data's is monitor/oversee by foreign facility from the state or someone.

and the owncloud-app can be a possibility for the jpaint to existing in the future too.
So, JPaint as Owncloud app, would be great !(For the Future too !)
Editing the own Pictures over the Net-on the own server ... wow..

Your handy Photograph in China or Falkland isle ,
syncing with the owncloud, then, go to the laptop,
open you owncloud and correcting/edit you Picture with jpaint.

nextcloud is the follower and is a branch of owncloud

best regards
Blacky

Does not support tertiary colour

One of these features is the ability to choose a tertiary colour. Everyone knows that you can left-click to select the foreground colour, and many people know that a right-click selects the background colour, but not too many people know that Ctrl+click allows one to select a tertiary colour. While painting, the tertiary colour can be accessed by holding Ctrl.

source: https://tpenguinltg.wordpress.com/2013/04/30/ms-paint-tricks-that-you-probably-didnt-know-you-could-do/

Where yall coming from?

people starring nearly every minute (on average / ish?)
3776 unique visitors today, up from y'know, like 7?

just curious... πŸ˜…

ImageΒ΄s resize mark points (blue)

In the modern theme i cannot see the imageΒ΄s resize mark points as you can see it in the classic theme. Is it possible to have the resize mark points (blue) in the modern theme? Attached is a picture. Also, in the modern theme when you start you cannot see the backgroundΒ΄s mark points (blue) to resize the image. Would it possible to put them as the classic theme?
ipaint

Making a selection should be passive

Simply making a selection should not modify the canvas; it should be a passive operation.
Only when you move or resize it (or otherwise operate on it) should it cut the selection from the canvas.

You can see that this is how it works in mspaint by creating a selection, then changing the secondary color, then dragging the selection, and the background color it leaves behind will be the new secondary color you selected.
In jspaint currently it leaves behind the original background color.

The way it works now causes or compounds problems, and makes the code more complicated (like, right now canceling the selection uses undo; stuff like that?).
I'll need to make sure operations like invert still work (on selections), and undo/redo and canceling

Storage not cleared

There seems to be a bug where Manage Storage doesn't clear items when you tell it to.
It acts like it's removing them, but if you reopen the dialog they're still there.

I can't reproduce this right now, but I could add a check for if the item was actually removed, and show an error message if it wasn't. It could be trying to remove a storage item with the wrong key so maybe I should also check that what it's trying to delete exists first? Or just make sure it uses the same key that it found the image stored with.

Fix dragging pasted selection when magnified

A pasted selection uses an img currently instead of a canvas, and I'm not sure why it would behave differently, but simply making it into a canvas does seem to fix this.

So I can fix this by just wrapping the image with Canvas() (a helper that I made), but it would probably be good to look for the root cause.

Incorrect mouse pointer for eraser tool

Environment: Chrome on Mac.
Steps to reproduce:

  • Selected "Eraser" tool, hovered over the work area
    Expected behaviour
  • mouse pointer should switch to eraser (square bar).
    Actual behaviour:
  • mouse pointer switched to "select" tool

Copy selection not implemented yet

Ok, this is just a nitpick.

In JSPaint, when you have a selection and hold shift and left click drag it, it creates a lot of copies. In MSPaint from Win XP there used to be this feature where you could press ctrl and left click drag to create just a single copy, i.e. something you'd use instead of select -> copy -> paste. Right now in JSPaint, holding ctrl and left click dragging a selection, just moves that selection away. In MSPaint, holding ctrl an left clicking was like "move this selection somewhere else, but still keep the original selection in place."

Here's an external source,

After selecting something, Ctrl + drag with the mouse - to create a copy of the selection.
https://winaero.com/blog/the-full-list-of-keyboard-shortcut-for-microsoft-paint/

Undo history

Great project!

I have however found one historically inaccurate bit: from what I remember, MS Paint didn't have undo history, limiting undo only to the last action. A quick search suggests it is indeed the case.

Perhaps jspaint should support a "historically precise" and "enhanced" modes?

Manage Storage UI

Removing a bunch of items is annoying. The remove buttons don't stay in the same place when removing things if the images are different sizes, and there's no remove all button.

Maybe it could be more elegant as a checkbox list type thing, either with a table header checkbox to select all or an explicit Select All button. Or, an explicit Remove All button; that would work too and be one less step.

Theme support

I really like the functionality of this app, but I would like to change it's icons and how the software looks like to match my environment. So a theme support would be nice!

package as native desktop app

This is really cool, but the limitations on copy and paste limit it's usefulness.

Ever thought of packaging it with one of the web -> desktop frameworks out there?
http://electron.atom.io/

There are APIs for clipboard access and you could get access to the file system proper. (I miss the old paint).

Fix flood fill algorithm

It should be a basic four-way flood fill.

Currently it doesn't get into all the corners, and even seems to be jumping gaps (down 2, left/right 1).

Test case, clicking in the center of this shape:

updated test case actual expected zoomed

updated test case actual expected

Dragging elements removes them

When you use the free-form selector, and move something - it disappears after dragging. I don't know what's causing this, I'll peek into the source and look for a solution.

β€”

Cool project by the way! πŸ‘ Good job!

Fix "Quick Undo"

When you're drawing a shape or stroke and you press the opposite mouse button, i.e. L+R together, it's supposed to immediately cancel the drawing operation. For touch, it should cancel if you touch with a second finger.

I actually had it working using the same code for mouse and touch, listening for both interactions as a secondary pointerdown. But that's not how pointer events work anymore for mouse presses. Either Chrome updated or I updated PEP and now a mouse will only have one "pointer" down at a time, but will get pointermove events (which ought to be called pointerchange) with button/buttons.

I tried to reimplement this, but while it fixed it on Chrome, it broke it on Firefox.

Transparent background in selections doesn't work

To reproduce this bug:

  • use the default colours (e.g. black foreground colour and a white background colour)
  • draw a black line on white background, for example with the Brush tool
  • select the Select tool, and click on image depicting a transparent background
  • draw a selection box around the black line
  • move the selection somewhere

Expected behaviour: The white colour in the background of the selection is transparent.
Actual behaviour: The white colour in the background of the selection is opaque.

Can lose image when out of memory, including auto-save if you interact

When chrome runs out of memory, canvases get erased, and when you see your image is gone you'll probably naturally try to undo (speaking from experience), but that actually then destroys the last memory of the image, because it saves over the autosave with more nothingness.

So: we should detect if the canvas suspiciously becomes blank. Maybe also the undo history canvases (just the one, when undoing/redoing, I guess should work).

I wonder if there's a good way to simulate out of memory conditions, rather than just loading web apps with terribly expensive memory usage.

Selection is basically invisible on dark backgrounds

Completely invisible on a black background.

Should do what mspaint does in Win95/98, which I didn't see before, just looking at screenshots:

ms win95 selection outline

ms win98 selection outline

(Although mspaint doesn't really make the handles visible on a dark background...)

Checkered background for transparency

When transparency is enabled, the canvas simply bleeds into the surrounding area. It can be hard to see where the edge of the canvas is especially when zoomed in. (The handles might not be on screen.)

It's also especially problematic when trying to edit images with that particular color of gray, which I've naturally come across working on jspaint, such as with the scrollbar arrow icons:

The checker pattern should be independent of the zoom.

Editing background

Hello!
Thank you for showing and sharing this tool. I would like to start the paint with a specific image, do you know from where exactly I can change the white background for a picture that I have made?
Thank you very much for your help!

Black&White mode not working as intended.

When changing Image -> Attributes -> Colors -> Black & White
A few things don't work correctly.

Namely the patterns don't draw the same way they did in the original paint, appear to work more like brushes. A single click with the paint tool produces the expected pattern, but clicking and dragging 'smears' the pattern.
Same issue applies with the line tool.

Rectangle appears to draw the border correctly, but is unable to fill.
Polygon does not draw the border correctly and cannot fill.

Same issues apply for ellipse and rounded rectangle.

The paint bucket/fill does not appear to work with the black & white patterns either.

Seeing as how airbrush works just fine with the black & white patterns I don't believe it would be too difficult to add that functionality to the paint tool.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.