jspaint/index.html

494 lines
24 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JS Paint</title>
<link href="styles/normalize.css" rel="stylesheet" type="text/css">
<link href="styles/layout.css" class="flippable-layout-stylesheet" rel="stylesheet" type="text/css">
<link href="styles/print.css" rel="stylesheet" type="text/css" media="print">
<link href="lib/os-gui/layout.css" class="flippable-layout-stylesheet" rel="stylesheet" type="text/css">
<!-- <link href="lib/os-gui/windows-98.css" rel="stylesheet" type="text/css"> -->
<!-- <link href="lib/os-gui/windows-default.css" rel="stylesheet" type="text/css" title="Windows Default"> -->
<!-- <link href="lib/os-gui/peggys-pastels.css" rel="alternate stylesheet" type="text/css" title="Peggy's Pastels"> -->
<!-- <link href="lib/tracky-mouse/tracky-mouse.css" rel="stylesheet" type="text/css"> -->
<link rel="apple-touch-icon" href="images/icons/apple-icon-180x180.png">
<!-- Chrome will pick the largest image for some reason, instead of the most appropriate one. -->
<!-- <link rel="icon" type="image/png" sizes="192x192" href="images/icons/192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="images/icons/32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="images/icons/96x96.png"> -->
<!-- <link rel="icon" type="image/png" sizes="16x16" href="images/icons/16x16.png"> -->
<link rel="shortcut icon" href="favicon.ico">
<link rel="mask-icon" href="images/icons/safari-pinned-tab.svg" color="red">
<link rel="manifest" href="manifest.webmanifest">
<meta name="msapplication-TileColor" content="#008080">
<meta name="msapplication-TileImage" content="images/icons/ms-icon-144x144.png">
<meta name="theme-color" content="#000080">
<meta name="viewport" content="width=device-width, user-scalable=no">
<meta name="description" content="Classic MS Paint in the browser, with extra features" />
<meta property="og:image:width" content="279">
<meta property="og:image:height" content="279">
<meta property="og:description" content="Classic MS Paint in the browser, with extra features.">
<meta property="og:title" content="JS Paint">
<meta property="og:url" content="https://jspaint.app">
<meta property="og:image" content="https://jspaint.app/images/icons/og-image-279x279.jpg">
<meta name="twitter:title" content="JS Paint">
<meta name="twitter:description" content="Classic MS Paint in the browser, with extra features">
<meta name="twitter:image" content="https://jspaint.app/images/meta/twitter-card-plz-no-crop.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@isaiahodhner">
<meta name="twitter:creator" content="@isaiahodhner">
<script>
// use only ES5 syntax for this script
// set up basic global error handling, which we can override later
window.onerror = function (msg, url, lineNo, columnNo, error) {
var string = msg.toLowerCase();
var substring = "script error";
if (string.indexOf(substring) > -1) {
alert('Script Error: See Browser Console for Detail');
} else {
try {
// try-catch in case of circular references or old browsers without JSON.stringify
error = JSON.stringify(error);
} catch (e) {}
alert('Internal application error: ' + msg + '\n\n' + 'URL: ' + url + '\n' + 'Line: ' + lineNo + '\n' + 'Column: ' + columnNo);
}
return false;
};
</script>
<script src="src/theme.js"></script>
</head>
<body>
<div id="about-paint" style="display: none">
<h1 id="about-paint-header">
<img src="images/icons/32x32.png" width="32" height="32" id="paint-32x32" alt=""/>
<span id="jspaint-project-name">JS Paint</span>
<!-- <small id="jspaint-version" title="expect bugs!">Alpha</small> -->
<button id="view-project-news">What's New?</button>
</h1>
<div id="maybe-outdated-view-project-news" hidden>
<div id="maybe-outdated-line">
<div id="outdated" hidden>
<div class="on-official-host">
There's a new version of JS Paint. <a id="refresh-to-update" href=".">Refresh</a> to get it.
</div>
<div class="on-third-party-host">
This instance of JS Paint is outdated compared to <a href="https://jspaint.app" target="_blank">jspaint.app</a>.
</div>
<div class="on-dev-host">
This version of JS Paint is outdated compared to <a href="https://jspaint.app" target="_blank">jspaint.app</a>.
</div>
</div>
<div id="checking-for-updates" hidden>
Checking for updates...
</div>
<div id="failed-to-check-if-outdated" hidden>
Couldn't check for updates.
<span class="navigator-offline">You're offline.</span>
<span class="navigator-online">JS Paint may be outdated.</span>
</div>
</div>
</div>
<p>JS Paint is a web-based remake of MS Paint by <a href="https://isaiahodhner.io/">Isaiah Odhner</a>.</p>
<p>Read about the project and extra features on <a href="https://github.com/1j01/jspaint#readme">the readme</a>.</p>
<p>Request features and report bugs <a href="https://github.com/1j01/jspaint/issues">on GitHub</a>
or <a href="mailto:isaiahodhner@gmail.com?subject=JS%20Paint">by email</a>.</p>
<p>Support the project at <a href="https://www.paypal.me/IsaiahOdhner" target="_blank">paypal.me/IsaiahOdhner</a>.</p>
</div>
<div id="news" hidden>
<article class="news-entry" id="news-2020-accessibility-update">
<h1>The Accessibility Update</h1>
<time datetime="2020-12-20">2020-12-20</time>
<img width="965" height="399" style="max-width: 100%; height: auto; image-rendering: auto;" alt="Hello in several languages, eye gaze guiding a cursor, and a sea lion barking into a microphone." src="https://i.postimg.cc/j29yrZbm/untitled-23.png"/>
<h2>Multi-Lingual Support</h2>
<p>JS Paint is now largely localized into 26 languages.</p>
<p>
How am I releasing so many languages at the initial release of multi-lingual support, you may ask?
Well, this project has the somewhat unique opportunity to reuse localizations from an existing program, since it's primarily a remake of MS Paint.
</p>
<p>
I downloaded and installed <a target="_blank" href="https://postimg.cc/4Y1V24wN">26 versions of Windows 98 in virtual machines</a>,
and extracted text from mspaint.exe in each one of them,
using a set of scripts that I wrote to to help me automate the process.
</p>
<p>
To change the language, go to <b>Extras > Language</b>.
Your preferred language may already be detected, if specified in system or browser settings.
</p>
<img width="1280" height="720" style="max-width: 100%; height: auto; image-rendering: auto;" alt="26 languages, right off the bat!" src="https://i.postimg.cc/G2bH92fp/language-menu.png"/>
<p>For Arabic and Hebrew, right-to-left layout is supported!</p>
<p>I tried my hand at some Arabic calligraphy...</p>
<img width="1141" height="800" style="max-width: 100%; height: auto; image-rendering: auto;" alt="Calligraphy where the shapes of the tools in Paint make up Arabic words for them." src="https://i.postimg.cc/NFX2TTp1/untitled-45.png"/>
<p>
If you want to contribute translations, <a target="_blank" href="https://github.com/1j01/jspaint/issues/80">get in touch!</a>
I need to do some technical work to set up for community translations on a public platform,
but I'm glad people have already expressed interest in helping translate!
(I also want to simplify the language in various parts of the UI before asking people to translate them.)
</p>
<h2>Eye Gaze Mode</h2>
<img width="511" height="360" style="max-width: 100%; height: auto; image-rendering: auto;" alt="You can use eye gaze or head movements to control the cursor." src="https://i.postimg.cc/2yC137gc/20.png"/>
<p>Eye Gaze Mode lets you control JS Paint without using your hands.</p>
<p>It's intended for use with an eye tracker, head tracker, or other coarse input scenario.</p>
<p>You don't need a thousand-dollar eye tracker device to play around with this, just a webcam and some free software.</p>
<p>
I recommend <a target="_blank" href="https://eviacam.crea-si.com/">Enable Viacam</a>, which is <em>not</em> an eye gaze tracker,
but rather a general video movement tracker that you can set up to track your head movement (or your torso or hand or anything else).
</p>
<p>
Eye tracking via a webcam has a ways to go, but it's also pretty amazing in its own right.
Try <a target="_blank" href="https://sourceforge.net/projects/gazepointer/">GazePointer</a>.
</p>
<p>
Eye gaze tracking requires significant calibration, and if the calibration is off,
it's hard to use because you can't look where you want to look to interact with things.
This is why I recommend head tracking (if that's an option for you),
because then you can freely look around, and control the cursor <em>independently</em>,
so if it gets offset, you can just tilt your head a bit.
</p>
<p>
Eye Gaze Mode is built mainly for people with movement disabilities like ALS or Cerebral Palsy,
but it can also just be a sort of magical experience.
It can also be frustrating, and takes some practice to master.
</p>
<p>
A good place to start is coloring line art using just the Fill tool (<img src="help/p_paint.gif" width="15" height="11" alt="">):
</p>
<ul>
<li>
<a target="_blank" href="https://duckduckgo.com/?t=canonical&q=coloring+pages&atb=v232-1&iax=images&ia=images">Find coloring pages online</a>,
and copy and paste them into JS Paint.
</li>
<li>
You can convert them to black and white in <b>Image > Attributes</b>, and then switch back to Colors.
(This makes it work better with the Fill tool.)
</li>
<li>
Enable Eye Gaze Mode with <b>Extras > Eye Gaze Mode</b> and note that it will start clicking where you hover.
You can disable this dwell clicking with the eye icon in the bottom of the screen.
</li>
<li>
Make the image fill the screen with <b>View > Zoom > Zoom To Window</b>.
</li>
</ul>
<p>
<span class="new">Bonus:</span> Since I implemented a vertical color box for Eye Gaze Mode,
I decided to make this available as a separate option. Access with <b>Extras > Vertical Color Box</b>.
</p>
<h2>Speech Recognition</h2>
<img width="549" height="275" style="max-width: 100%; height: auto; image-rendering: auto;" alt="The sea lion says &quot;Art! Art! Art!&quot; into a microphone." src="https://i.postimg.cc/BnQ8cpY2/Art-Art-Art.png"/>
<p>
Using only your voice, you can switch tools and colors, pan the view, click on buttons on the screen by name, and use most menu items.
You can even say "draw a cat in a party hat" to have JS Paint try to sketch a cat in a party hat.
</p>
<p>This feature pairs well with Eye Gaze Mode for a more complete hands free experience.</p>
<p>
The feature is only available on Chrome, and only understands English.
Note that Chrome sends your voice to Google servers.
</p>
<p>Access with <b>Extras > Speech Recognition</b>. If this option is grayed out, your browser is not supported.</p>
<p>JS Paint will show what it thinks you said in the status bar at the bottom of the screen.</p>
<p>
There are many synonyms for commands, and often you can do things with very short phrases like "Curve" to switch to the Curve tool.
If it's not recognizing your voice for short commands like "Curve" or "Cut", you may want to try longer phrases like "Curve tool" or "Cut selection",
as this helps it distinguish the sound as speech, rather than a cough for instance.
</p>
<h2>Edit Colors Dialog</h2>
<p>I also implemented the Edit Colors dialog. Previously this used the native system color picker, and didn't work for some people.</p>
<p>Access with <b>Colors > Edit Colors</b> or double click a color in the palette to edit.</p>
<figure>
<img width="496" height="350" style="max-width: 100%; height: auto; image-rendering: auto;" src="https://i.postimg.cc/cLNgWH0r/Peek-2020-12-04-00-31.gif"/>
<figcaption>An animation morphing between JS Paint and MS Paint's color picking dialog. It's pretty close, other than the font.</figcaption>
</figure>
<p>Keyboard shortcuts are supported in this dialog, and for mobile devices with small screens, I made it treat adding custom colors as a separate screen.</p>
<h2>Conclusion</h2>
<p>
JS Paint should be way more accessible now. And futuristic.
<p>
<p>
Of course there's always more that could be done.
Eye Gaze Mode could use brush stroke smoothing, and Speech Recognition could use Artificial General Intelligence.
</p>
<p>
I'd love to see people using JS Paint, especially the Eye Gaze Mode and Speech Recognition,
so if you record a video of using JS Paint, please
<a target="_blank" href="https://docs.google.com/forms/d/e/1FAIpQLSdGgS6TS4wBV89v8NoYHenh1eI8jYBfgwYBdPx-OaCEG5EW7g/viewform?usp=sf_link">send it to me through this form.</a>
This lets me know what's actually important to people, and what's confusing,
and it gives me motivation to work on new features.
</p>
</article>
<article class="news-entry" id="news-2019-winter-update">
<h1>Winter Update</h1>
<time datetime="2019-12-20">2019-12-20</time>
<img width="563" height="334" style="max-width: 100%; height: auto; image-rendering: auto;" alt="" src="https://i.postimg.cc/63Wc6vpG/2019-winter-update-candy-cane.gif"/>
<h2>Winter Theme</h2>
<p>
A new UI skin is available, under <b>Extras &gt; Themes &gt; Winter</b>, featuring winter and holiday icons, festive fonts, and a palette with seasonal colors and peppermint patterns.
</p>
<img width="256" height="16" alt="" src="images/winter/tools.png"/>
<p>
Merry Christmas and happy Hanukkah!
</p>
<h2>Better History</h2>
<b class="new">New:</b> Jump to any point in the document's history, forwards or backwards, with <b>Edit &gt; History</b> or <kbd>Ctrl+Shift+Y</kbd>.
<ul>
<li>
Click on Text in the history view to go back to text editing.
</li>
<li>
You can return to when a selection existed.
</li>
<li>
Note: these states are skipped over with normal Undo and Redo, so you need to use the History window.
</li>
<li>
Branching history: if you undo, and then make changes, you can get back to everything.
Future states are preserved.
</li>
</ul>
<b class="bad">Warning:</b> History is not saved with the autosave. Document history will be lost if you refresh the page, or close the tab, or if the tab crashes, or if you close or restart your browser, or likely if you're just on a phone and the mobile browser loses focus.
<h2>Improved Mobile Support</h2>
<p>
<b class="new">New:</b> Use two fingers to pan the view.
</p>
<p>
I recently made it easier to grab handles for resizing things.
With that, combined with multitouch panning,
JS Paint is much more useable on a phone.
</p>
<p><b class="bad">Caveat:</b> It's slow on some devices, and parts of the interface are still too small for touch.</p>
</article>
<article class="news-entry" id="news-2019-polygon-text-and-select">
<h1>Polygon, Text, and Select</h1>
<time datetime="2019-12-04">2019-12-04</time>
<p>
Handles are now way easier to drag, with extended click targets, similar to Paint from Windows 7.
It's not unreasonable to use with a touch screen now!
This applies to selections, textboxes, and the main canvas handles.
</p>
<p>
Resizing things while zoomed in is <a target="_blank" href="https://github.com/1j01/jspaint/issues/13#issuecomment-562247085">finally fixed</a>!
</p>
<p>
The Text tool now perfectly previews the pixels that will be placed on the canvas.
What you see is what you get!
Also it retains all browser editing behavior, like spellcheck,
using a convoluted, yet elegant overlaying strategy.
(I prototyped this <a target="_blank" href="https://jsfiddle.net/1j01/wnac09u3/">here</a>
and <a target="_blank" href="https://jsfiddle.net/1j01/qkvfjn1r/">here</a> if you're interested.)
</p>
<p>
With the fill-only option selected, the Polygon tool now previews with inverted lines, like MS Paint does.
(When you finish the polygon, the boundary of the shape matches the preview exactly,
because it actually <em>does</em> draw a stroke, just the same color as the fill.)
</p>
</article>
<article class="news-entry" id="news-2019-zoom-viewport">
<h1>Zoom To Mouse</h1>
<time datetime="2019-10-26">2019-10-26</time>
<p>
<b class="new">New:</b> The Magnifier now lets you zoom to a specific location,
showing a preview of the new viewport.
</p>
<p>
Also, when zooming out with the Magnifier,
or changing the zoom from the toolbar or menus,
the top left corner of the viewport is now kept anchored.
</p>
<p>
Also, pasting a selection will now go to the top left of the viewport,
instead of the entire document.
</p>
</article>
<article class="news-entry" id="news-2019-grid-zoom-cursors">
<h1>The Grid, Custom Zoom, and Dynamic Cursors</h1>
<time datetime="2019-10-09">2019-10-09</time>
<p>
<b class="new">New:</b> The Grid. Zoom to 4x+ and use <b>View &gt; Zoom &gt; Show Grid</b> or <kbd>Ctrl+G</kbd> to enable.
This works with browser zoom as well to provide crisp gridlines even if you zoom in with your browser.
</p>
<p>
<b class="new">New:</b> <b>View &gt; Zoom &gt; Custom Zoom</b>,
including an actually-custom numerical zoom option, unlike MS Paint.
</p>
<p>
<b class="new">New:</b> Dynamic cursors for brush and eraser,
so you now have a preview of exactly where the tool will draw.
</p>
<p>
Also, in the event that your browser clears canvases to free up memory,
you should be more likely to be able to undo to get back to a useful state.
</p>
</article>
<article class="news-entry" id="news-2019-async-clipboard">
<h1>Full Clipboard Support</h1>
<time datetime="2019-09-21">2019-09-21</time>
<p>
JS Paint now lets you copy real image data to the Clipboard, both with keyboard shortcuts and from the Edit menu.
This feature is available in Chrome 76+. Other browsers don't support it yet, as of Sep 2019.
</p>
<p>
Also: paste a URL of an image, and JS Paint will load it.
(This is an alternative to <b>File &gt; Load from URL</b>.)
</p>
</article>
<style>
#news {
background: white;
color: black;
}
.news-entry {
padding: 20px;
max-width: 563px;
}
.news-entry > h1 {
font-size: 1.3em;
margin: 0;
margin-bottom: 0.3em;
}
.news-entry > time {
font-size: 1.2em;
color: gray;
}
.news-entry > h2 {
font-size: 1.9em;
font-weight: normal;
margin: 0;
margin-bottom: 0.3em;
margin-top: 0.3em;
}
.news-entry .new {
color: green;
}
.news-entry .bad {
color: #d11a29;
}
</style>
</div>
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
crossorigin="anonymous"
></script>
<script>
window.jQuery || document.write('<script src="lib/jquery-3.4.1.min.js"><\/script>');
</script>
<script src="lib/gif.js/gif.js"></script>
<script src="lib/pako-2.0.3.min.js"></script> <!-- for UPNG.js and UTIF.js -->
<script src="lib/UPNG.js"></script>
<script src="lib/UTIF.js"></script>
<script src="lib/bmp.js"></script>
<script src="lib/pdf.js/build/pdf.js"></script>
<script src="lib/anypalette-0.6.0.js"></script>
<script src="lib/FileSaver.js"></script>
<script src="lib/font-detective.js"></script>
<script src="lib/libtess.min.js"></script>
<!-- <script src="lib/tracky-mouse/tracky-mouse.js"></script> -->
<script src="lib/os-gui/parse-theme.js"></script>
<script src="lib/os-gui/$Window.js"></script>
<script src="lib/os-gui/MenuBar.js"></script>
<script src="lib/imagetracer_v1.2.5.js"></script>
<script src="src/app-localization.js"></script> <!-- must not be async/deferred, as it uses document.write(); and must come before other app code which uses localization functions -->
<script src="src/msgbox.js"></script>
<script src="src/functions.js"></script>
<script src="src/helpers.js"></script>
<script src="src/storage.js"></script>
<script src="src/$Component.js"></script>
<script src="src/$ToolWindow.js"></script>
<script>
// after show_error_message, showMessageBox, and localize are defined,
// set up better global error handling
const old_onerror = window.onerror;
window.onerror = (message, source, lineno, colno, error)=> {
try {
show_error_message(localize("Internal application error."), error);
} catch (e) {
old_onerror(message, source, lineno, colno, error);
}
};
</script>
<script src="src/$ToolBox.js"></script>
<script src="src/$ColorBox.js"></script>
<script src="src/$FontBox.js"></script>
<script src="src/$Handles.js"></script>
<script src="src/OnCanvasObject.js"></script>
<script src="src/OnCanvasSelection.js"></script>
<script src="src/OnCanvasTextBox.js"></script>
<script src="src/OnCanvasHelperLayer.js"></script>
<script src="src/image-manipulation.js"></script>
<script src="src/tool-options.js"></script>
<script src="src/tools.js"></script>
<!--<script src="src/extra-tools.js"></script>-->
<script src="src/edit-colors.js"></script>
<script src="src/manage-storage.js"></script>
<script src="src/imgur.js"></script>
<script src="src/help.js"></script>
<script src="src/simulate-random-gestures.js"></script>
<script src="src/menus.js"></script>
<script src="src/speech-recognition.js"></script>
<script src="src/app.js"></script>
<script src="src/sessions.js"></script>
<script src="lib/konami.js"></script>
<script src="src/vaporwave-fun.js"></script>
<script>
if(/MSIE \d|Trident.*rv:/.test(navigator.userAgent)){
document.write(
'<style>body { text-align: center; }</style>' +
'<div className="not-supported">' +
'<h1 className="not-supported-header">Internet Explorer is not supported!</h1>' +
'<p className="not-supported-details">Try Chrome, Firefox, or Edge.</p>' +
'</div>'
);
}
</script>
<noscript>
<h1><img src="images/icons/32x32.png" width="32" height="32" alt=""/> JS Paint</h1>
<p>This application requires JavaScript to run.</p>
<p>
Assuming this is the official instance of jspaint,
at <a href="https://jspaint.app">https://jspaint.app</a>,
you can safely enable JavaScript.
</p>
<p>You can also check out <a href="https://github.com/1j01/jspaint">the source code and project info</a>.</p>
</noscript>
<svg style="position: absolute; pointer-events: none; bottom: 100%;">
<defs>
<filter id="disabled-inset-filter" x="0" y="0" width="1px" height="1px">
<feColorMatrix
in="SourceGraphic"
type="matrix"
values="
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
-1000 -1000 -1000 1 0
"
result="black-parts-isolated"
/>
<feFlood result="shadow-color" flood-color="var(--ButtonShadow)"/>
<feFlood result="hilight-color" flood-color="var(--ButtonHilight)"/>
<feOffset in="black-parts-isolated" dx="1" dy="1" result="offset"/>
<feComposite in="hilight-color" in2="offset" operator="in" result="hilight-colored-offset"/>
<feComposite in="shadow-color" in2="black-parts-isolated" operator="in" result="shadow-colored"/>
<feMerge>
<feMergeNode in="hilight-colored-offset"/>
<feMergeNode in="shadow-colored"/>
</feMerge>
</filter>
</defs>
</svg>
</body>
</html>