JS油漆基于像素完美的基于Web的MS Paint Remake等...尝试一下!然后加入Discord服务器分享您的艺术品!
JS Paint重新创建了MS油漆的每个工具和菜单,甚至还鲜为人知的功能,以高度的保真度。
它支持主题,其他文件类型和可访问性功能,例如眼目光模式和语音识别。

啊,是的,好老油漆。不是带有丝带或新的Skeuomormormormormorphic的界面的那个,可以占据近一半的屏幕。 (而不是更新的油漆3D。)
Windows 95、98和XP是涂料的黄金年。您有一个工具箱和一个颜色盒,一个前景颜色和背景颜色,这就是您所需要的。
事情很简单。
但是我们想撤消三个以上的动作。我们想编辑透明图像。我们不能只继续使用旧油漆。
这就是为什么我要制作JS油漆。我想把优质的旧油漆带入现代时代。
编辑功能:
其他改进:

使用工具的一些事情尚未完成。请参阅todo.md
Web应用程序中的完整剪贴板支持需要一个浏览器支持带有images的异步剪贴板API,即在撰写时76+ Chrome。
在其他浏览器中,您仍然可以使用CTRL+C复制,用CTRL+X切割,然后用Ctrl+V粘贴,但是从JS涂料中复制的数据只能粘贴到其他JS涂料的实例中。外部图像可以粘贴到中。
与MS Paint不同,您可以使用Edit> Undo来恢复节省的色彩或质量降低。这不会撤消文件,但允许您使用文件>另存为更高质量的不同格式。
建议保存为PNG,因为它可以在保留全质量的同时提供小文件尺寸。
| 文件扩展 | 姓名 | 读 | 写 | 阅读调色板 | 写调色板 |
|---|---|---|---|---|---|
| .png | PNG | ✅ | ✅ | ||
| .bmp,.dib | 单色位图 | ✅ | ✅ | ✅ | |
| .bmp,.dib | 16颜色位图 | ✅ | ✅ | ✅ | |
| .bmp,.dib | 256颜色位图 | ✅ | ✅ | ✅ | |
| .bmp,.dib | 24位位图 | ✅ | ✅ | N/A。 | N/A。 |
| .tif,.tiff,.dng,.cr2,.nef | TIFF(加载第一页) | ✅ | ✅ | ||
| PDF(加载第一页) | ✅ | ||||
| .webp | WebP | ||||
| .gif | GIF | ||||
| .jpeg,.jpg | jpeg | N/A。 | N/A。 | ||
| .svg | SVG(仅默认尺寸) | ||||
| .ico | ICO(仅默认尺寸) |
当前,标记的功能保留到浏览器中以支持或不支持。如果标记了“写入”,则格式将出现在文件类型下拉列表中,但在尝试保存时可能不起作用。有关打开文件,请参见Wikipedia的浏览器映像格式支持表,以获取更多信息。
标记的功能可能很快即将到来,不适用的N/A手段。
“ Read Palette”是指自动将颜色加载到颜色框中(从索引的颜色图像),“ Write Palette”是指编写索引的颜色图像。
使用颜色>保存颜色和颜色>获得颜色,您可以以许多不同格式保存和加载颜色,以兼容各种程序。
如果您想在另一个应用程序中添加广泛的调色板支持,我将此功能作为库可用:
Anypalette.js
| 文件扩展 | 姓名 | 程序 | 读 | 写 |
|---|---|---|---|---|
| 。朋友 | 即兴调色板 | Windows 95和Windows NT 4.0的MS油漆 | ✅ | ✅ |
| .gpl | gimp调色板 | gimp,inkscape,krita,kolourpaint,scribus,cinepaint,mypaint | ✅ | ✅ |
| .aco | Adobe颜色色板 | Adobe Photoshop | ✅ | ✅ |
| .ase | Adobe Swatch Exchange | Adobe Photoshop,Indesign和Illustrator | ✅ | ✅ |
| 。TXT | paint.net调色板 | paint.net | ✅ | ✅ |
| 。行为 | Adobe颜色表 | Adobe Photoshop和Illustrator | ✅ | ✅ |
| .pal,.psppalette | 油漆店Pro调色板 | Paint Shop Pro(JASC软件 / Corel) | ✅ | ✅ |
| .hpl | 家庭调色板 | Allaire Homesite / Macromedia Coldfusion | ✅ | ✅ |
| 。CS | Colorschemer | Colorschemer Studio | ✅ | |
| 。朋友 | 星际船调色板 | 星际争霸 | ✅ | ✅ |
| .wpe | 星形地形调色板 | 星际争霸 | ✅ | ✅ |
| .sketchpalette | 草图调色板 | 草图 | ✅ | ✅ |
| .spl | 滑雪调色板 | Skencil(以前称为素描) | ✅ | ✅ |
| .soc | Staroffice颜色 | Staroffice,OpenOffice,Libreoffice | ✅ | ✅ |
| 。颜色 | KolourPaint Color Collection | Kolourpaint | ✅ | ✅ |
| 。颜色 | 等离子桌面配色方案 | KDE等离子桌面 | ✅ | |
| 。主题 | Windows主题 | Windows桌面 | ✅ | |
| .themepack | Windows主题 | Windows桌面 | ✅ | |
| .css,.scss,.styl | 级联样式表 | 网络浏览器 /网页 | ✅ | ✅ |
| .html,.svg,.js | 任何具有CSS颜色的文本文件 | 网络浏览器 /网页 | ✅ |
有一个黑色和白色模式,带有图案,而不是调色板中的颜色,您可以从图像>属性...
如果您在正确的位置抓住颜色框和工具箱,则可以将其拖动。您甚至可以将它们拖到小窗户中。您可以双击其标题栏,将窗户放回侧面。
除了左键单击前景颜色和右键单击背景颜色外,还可以通过绘制CTRL访问第三种颜色。它从没有颜色开始,因此您需要保持CTRL并首先选择颜色。关于此颜色插槽的奇特之处在于,您可以在绘制时按和释放CTRL以切换颜色。
您可以将图像转换(例如翻转/旋转,伸展/偏斜或反转(在图像菜单中)应用于整个图像或选择。尝试使用自由形式的选择工具涂抹,然后进行图像>倒置
这些MS油漆教程中的技巧和技巧也可以在JS Paint中起作用:
JS油漆可以安装为渐进式网络应用程序(PWA),尽管它尚未脱机。在地址栏中查找安装提示。
PWA功能:
缺少功能:
我还将其置于带电子和电子锻造的桌面应用程序中。您可以从“发行”页面下载它。

电子应用功能:
jspaint path/to/file.pngeditor_window.on("close")调用preventDefault ,可能是一个功能,但需要显示/聚焦窗口克隆仓库。
如果没有它,请安装Node.js,然后在项目目录中打开命令提示符 /终端。
运行npm run lint以检查拼写错误,类型错误,代码样式问题和其他问题。
运行npm run format以自动修复格式问题,或npx eslint --fix修复所有可自动固定问题的文件。
格式规则配置为与VS Code的内置格式化器的兼容性。
运行npm test以使用柏树运行基于浏览器的测试。 (不幸的是,启动和运行测试很慢。)
运行npm run accept以接受任何视觉更改。不幸的是,这重新运行了所有测试,而不是接受先前测试的结果,因此您最终可能会得到与以前的测试不同的结果。如果使用GitHub桌面,则可以以四种不同的模式查看图像的差异。
要打开Cypress UI,请先运行npm run test:start-server ,然后同时进行npm run cy:open
测试还与Travis CI连续集成。
使用npm i安装依赖关系后,请使用npm run dev启动实时填充服务器。
确保在layout.css中使用任何布局重要样式。在更新layout.css时,使用RTLCSS生成了样式表的左右版本。
您应该通过将语言更改为阿拉伯语或希伯来语来测试RTL布局。转到Extras>“语言>ال样”或“עברעבר” 。
有关如何控制RTL布局,请参见控制指令。
有一个VS代码启动任务,用于附加到Chrome进行调试。有关使用说明,请参见.vscode/launch.json 。
npm i安装依赖项npm run electron:start包括电子选,因此您可以使用F5 / Ctrl+R进行重新加载,而F12 / Ctrl+Shift+I打开DevTools。
您可以使用npm run electron:make
有一个VS代码启动任务,用于调试电子主过程。对于渲染器过程,您可以使用嵌入式的Chrome DevTools。
可以使用常规Web服务器部署JS涂料。
没有什么需要编译的。
可选地,如果将URL粘贴到JS Paint中,则可以设置Anywhere Server的CORS Anywhere Server,或使用#load:<URL>功能,其中不在同一域上。
默认情况下,它将使用设置的Any Ancome cors使用cors。
它是在Heroku上免费托管的,您可以设置自己的实例并将其配置为与自己的域一起使用。
您必须使用自己的实例URL查找并替换https://jspaint-cors-proxy.herokuapp.com 。
多人支持当前依赖于不是开源软件的Firebase。
您可以创建一个Firebase实时数据库实例,并编辑JS Paint的sessions.js指向它,在设置Web应用程序时,用Firebase Console替换了传递给initializeApp config 。
但是到目前为止,多人游戏模式非常卑鄙。应该用开源的东西代替,更安全,更高效,更健壮。
将其添加到您的HTML:
< iframe src =" https://jspaint.app " width =" 100% " height =" 100% " > </ iframe > 您可以通过向URL添加#load:<URL>从URL加载图像。
< iframe src =" https://jspaint.app#load:https://jspaint.app/favicon.ico " width =" 100% " height =" 100% " > </ iframe >如果要控制JS涂料,如何保存/加载文件或直接访问画布,则有一个不稳定的API。
首先,您需要克隆回购,因此您可以将iframe指向本地副本。
JS油漆的本地副本必须托管与包含页面同一Web服务器,或者更具体地说,必须共享相同的来源。
拥有本地副本也意味着API随时都不会破裂。
如果将JS涂料克隆到一个名为jspaint的文件夹中,该文件夹与要嵌入的页面相同的文件夹中,您可以使用此信息:
< iframe src =" jspaint/index.html " id =" jspaint-iframe " width =" 100% " height =" 100% " > </ iframe >如果它居住在其他地方,则可能需要添加../到路径的开始,才能上升一个水平。例如, src="../../apps/jspaint/index.html" 。您也可以使用绝对URL,例如src="https://example.com/cool-apps/jspaint/index.html" 。
您可以使用JS Paint的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 >斑点代表内存中文件的内容。
文件句柄是可以识别文件的任何东西。您可以拥有此概念,并定义如何识别文件。从索引到数组到Dropbox文件ID,再到IPFS URL,再到文件路径,它可能是任何东西。我忘记了它可以是任何类型,也许它可能是字符串。
一旦有了文件句柄的概念,就可以使用系统挂钩实现文件拾取器,以及函数以读写文件。
| 命令 | 使用的钩子 |
|---|---|
| 文件>另存为 | systemHooks.showSaveFileDialog ,然后选择文件时, systemHooks.writeBlobToHandle |
| 文件>打开 | systemHooks.showOpenFileDialog ,然后选择文件时, systemHooks.readBlobFromHandle |
| 文件>保存 | systemHooks.writeBlobToHandle (或与文件相同>保存,如尚未打开文件) |
| 编辑>复制到 | systemHooks.showSaveFileDialog ,然后选择文件时, systemHooks.writeBlobToHandle |
| 编辑>粘贴 | systemHooks.showOpenFileDialog ,然后选择文件时, systemHooks.readBlobFromHandle |
| 文件>设置为墙纸(瓷砖) | systemHooks.setWallpaperTiled如果定义,else systemHooks.setWallpaperCentered如果定义,则与文件>另存为 |
| 文件>设置为墙纸(中心) | systemHooks.setWallpaperCentered如果定义,则与文件>另存为 |
| Extras>渲染历史为GIF | 与文件相同>另存为 |
| 颜色>保存颜色 | 与文件相同>另存为 |
| 颜色>获得颜色 | 与文件相同>打开 |
要使用加载用于编辑的文件启动该应用程序,请等待应用程序加载,然后使用文件句柄systemHooks.readBlobFromHandle调用,并告诉应用程序加载该文件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 ) ;
} ) ;这很笨拙,将来可能会有一个查询字符串参数来通过其句柄加载初始文件。 (自我注意:它需要等待您的系统挂钩注册,以某种方式。)
已经有一个查询字符串参数可以从URL加载:
< iframe src =" https://jspaint.app?load:SOME_URL_HERE " > </ iframe >但这不会设置用于保存的文件句柄。
您可以定义两个功能来设置墙纸,将通过文件>设置为墙纸(瓷砖)和文件>设置为墙纸(中心) 。
systemHooks.setWallpaperTiled = (canvas) => { ... };systemHooks.setWallpaperCentered = (canvas) => { ... };如果仅定义systemHooks.setWallpaperCentered ,JS Paint将尝试猜测屏幕的尺寸并铺图像,并通过调用systemHooks.setWallpaperCentered功能应用它。
如果您不指定systemHooks.setWallpaperCentered ,则使用systemHooks.showSaveFileDialog和systemHooks.writeBlobToHandle保存文件( <original file name> wallpaper.png )。
这是一个完整的示例,支持持续的自定义墙纸作为包含页面上的背景:
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 ) ;
}有点递归,对不起;可能会更简单。就像仅使用数据URL。 (实际上,我认为我想使用blob URL,以免它用超长的URL膨胀DOM Insportor。这实际上是DevTools UX错误。也许他们已经改善了这一点?)
您可以加载具有所需尺寸的文件。目前没有特殊的API。
请参阅最初加载文件。
您可以以编程方式更改主题:
var iframe = document . getElementById ( "jspaint-iframe" ) ;
var jspaint = iframe . contentWindow ;
jspaint . set_theme ( "modern.css" ) ;但这将破坏用户的喜好。
Extras>“主题”菜单仍然可以使用,但是在重新加载页面时,偏好不会持续。
将来可能会有一个查询字符串参数来指定默认主题。您还可以分配J -Spraint更改默认主题。
与主题类似,您可以尝试以编程方式更改语言:
var iframe = document . getElementById ( "jspaint-iframe" ) ;
var jspaint = iframe . contentWindow ;
jspaint . set_language ( "ar" ) ;但这实际上会要求用户重新加载应用程序以更改语言。
Extras>“语言”菜单仍将起作用,但是每次重新加载页面时,用户都会不愿更改语言。
将来可能会有一个查询字符串参数来指定默认语言。您还可以分配J -Spraint来更改默认语言。
尚未支持。您可以分叉J -Spraint并添加自己的菜单。
通过访问画布,您可以实现图纸的实时预览,例如,实时更新游戏引擎中的纹理。
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" ) ;建议不要将其用于加载文档,因为它不会更改文档标题或重置撤消/重做历史记录等。而是使用open_from_file 。
如果您想制作按钮或其他UI来对文档进行操作,则应该(可能)使其变得不可行。这很容易,只需将您的动作包裹在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
} ) ; systemHooks.showSaveFileDialog({ formats, defaultFileName, defaultPath, defaultFileFormatID, getBlob, savedCallbackUnreliable, dialogTitle })定义此功能以覆盖默认保存对话框。这既用于保存图像,调色板文件和动画。
参数:
formats :代表文件类型的对象数组,并具有以下属性:formatID :唯一标识格式的字符串(可能与mimeType相同)mimeType (可选):文件格式的指定媒体类型,例如"image/png" (调色板格式没有此属性)name :文件格式的名称,例如"WebP"nameWithExtensions :文件格式的名称,然后是扩展名列表,例如"TIFF (*.tif;*.tiff)"extensions :一个文件扩展名,不包括DOT,首选扩展名,例如["bmp", "dib"]defaultFileName (可选):建议的文件名,例如"Untitled.png"或打开文档的名称。defaultPath (可选):打开的文档的文件句柄,因此您可以轻松地将其保存到同一文件夹中。 MISNOMER:这可能不是一条路径,这取决于您如何定义文件句柄。defaultFileFormatID (可选):默认情况下选择文件格式的formatID 。async function getBlob(formatID) :您调用的函数以以一种受支持格式之一获取文件。它采用formatID ,并返回一个Promise ,该诺言用Blob代表要保存的文件内容。function savedCallbackUnreliable({ newFileName, newFileFormatID, newFileHandle, newBlob }) (可选):用户保存文件时调用的函数。 newBlob应该来自getBlob(newFileFormatID) 。dialogTitle (可选):保存对话框的标题。在此处注意控件的反转:JS Paint调用您的systemHooks.showSaveFileDialog函数,然后调用JS Paint的getBlob函数。一旦getBlob解决,您可以调用由JS涂料定义的savedCallbackUnreliable ploce函数。 (希望我将来能澄清一下。)
另请注意,此功能负责保存文件,而不仅仅是选择保存位置。如果有用,您可以重复使用systemHooks.writeBlobToHandle功能。
systemHooks.showOpenFileDialog({ formats })定义此功能以覆盖默认的打开对话框。这用于打开图像和调色板。
参数:
formats :与systemHooks.showSaveFileDialog相同请注意,此功能负责加载文件内容,而不仅仅是选择文件。如果有用,您可以重复使用systemHooks.readBlobFromHandle函数。
systemHooks.writeBlobToHandle(fileHandle, blob)定义此功能以告诉JS绘制如何保存文件。
参数:
fileHandle :系统定义的文件句柄,表示要写入的文件。blob :代表要保存的文件内容的Blob 。返回:
false使用true Promise ,或者如果不知道该文件是否成功保存,则undefined ,而使用<a href="..." download="..."> 。承诺不应拒绝;应通过显示错误消息并返回false来处理错误。 systemHooks.readBlobFromHandle(fileHandle)定义此功能以告诉JS油漆如何加载文件。
参数:
fileHandle :系统定义的文件句柄表示要读取的文件。 systemHooks.setWallpaperTiled(canvas)定义此功能以告诉JS油漆如何设置墙纸。有关示例,请参见集成集作为墙纸。
参数:
canvas :带有图像的HTMLCanvasElement将其设置为墙纸。 systemHooks.setWallpaperCentered(canvas)定义此功能以告诉JS油漆如何设置墙纸。有关示例,请参见集成集作为墙纸。
参数:
canvas :带有图像的HTMLCanvasElement将其设置为墙纸。 undoable({ name, icon }, actionFunction)用它使动作变得不可行。
此功能采用画布和其他一些状态的快照,然后调用actionFunction函数。它在历史上创建了一个条目,因此可以撤消。
参数:
name :动作的名称,例如"Brush"或"Rotate Image 270°"icon (可选):要在“历史记录”窗口中显示的Image 。建议使用15x11像素。actionFunction :不需要参数并修改画布的函数。 show_error_message(message, [error])使用它显示一个错误消息对话框,可选地使用可扩展的错误详细信息。
参数:
message :要在对话框中显示的纯文本。error (可选):在对话框中显示的Error对象,默认情况下在“详细信息”可扩展部分中倒塌。 open_from_file(blob, source_file_handle)用它将文件加载到应用程序中。
参数:
blob :代表要加载的文件的Blob对象。source_file_handle :系统定义的文件的相应文件句柄。对不起,古怪的API。 API是新的,部分根本没有设计。这只是我依靠的一个黑客,进入了JS涂料的内部以加载文件。我决定将其记录为API的第一个版本,因为无论如何在升级使用时,我想要一个更改。
set_theme(theme_file_name)用它来更改应用程序的外观。
参数:
theme_file_name :要加载的主题文件的名称,其中之一:"classic.css" :Windows98主题。"dark.css" :黑暗主题。"modern.css" :现代主题。"winter.css" :节日冬季主题。"occult.css" :撒旦主题。 set_language(language_code)您可以使用它来更改应用程序的语言。但是实际上,它将向用户提示更改语言,因为应用程序需要重新加载以应用更改。而且,如果该对话框不是正确的语言,那么它们可能会感到困惑。
参数:
language_code :要使用的语言代码,例如英语的"en" ,用于传统中文的"zh" , "zh-simplified" ,等等。 API会发生很多变化,但更改将在ChangElog中进行记录。
不仅是变化的历史,而且是迁移/升级指南。
有关通用项目新闻,请单击应用程序中的Extras>“项目新闻” 。
JS涂料是免费的开源软件,并根据MIT允许的MIT许可证许可。