<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>图像处理 on 方糖猫Blog</title>
    <link>https://ftmao.com/blog/tags/%E5%9B%BE%E5%83%8F%E5%A4%84%E7%90%86/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Fri, 18 May 2018 10:00:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/tags/%E5%9B%BE%E5%83%8F%E5%A4%84%E7%90%86/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>jphotoshop：用 Java Swing 手写一个 Photoshop——架构与算法全解析</title>
      <link>https://ftmao.com/blog/posts/jphotoshop-java-swing-photoshop-clone/</link>
      <pubDate>Fri, 18 May 2018 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/jphotoshop-java-swing-photoshop-clone/</guid>
      <description>&lt;h1 id=&#34;jphotoshop用-java-swing-手写一个-photoshop架构与算法全解析&#34;&gt;jphotoshop：用 Java Swing 手写一个 Photoshop——架构与算法全解析&lt;/h1&gt;
&lt;p&gt;把多年前的一个练手项目整理开源了：&lt;strong&gt;jphotoshop&lt;/strong&gt;，一个用纯 Java Swing 手写的 Photoshop 模拟器。不写不知道，写一个&amp;quot;简化版 PS&amp;quot;几乎把计算机图形学、数据结构和设计模式都过了一遍，这篇文章既是功能介绍，也是一次技术复盘。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开源地址：&lt;a href=&#34;https://gitee.com/soulnew/jphotoshop&#34;&gt;https://gitee.com/soulnew/jphotoshop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;技术栈：Java + Swing（Swing Application Framework / JSR-296），NetBeans Ant 工程&lt;/li&gt;
&lt;li&gt;规模：197 个 Java 文件，约 1.8 万行（自研约 1.4 万行），开发周期约半年&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一功能概览一个简化版-ps能做什么&#34;&gt;一、功能概览：一个&amp;quot;简化版 PS&amp;quot;能做什么&lt;/h2&gt;
&lt;p&gt;UI 形态上和老版 PS 一致：&lt;strong&gt;MDI 多文档界面&lt;/strong&gt;——每张图片一个内部编辑窗口，图层面板、导航缩放面板都是可拖动的浮动内窗。&lt;/p&gt;
&lt;h3 id=&#34;11-图层系统&#34;&gt;1.1 图层系统&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;多图层编辑：图像图层、&lt;strong&gt;文字图层&lt;/strong&gt;、浮动选区层（拖动选区内容时的临时层）；&lt;/li&gt;
&lt;li&gt;图层面板带 40×30 &lt;strong&gt;实时缩略图&lt;/strong&gt;，支持拖拽换序（基于 Swing TransferHandler 的 DnD）、复制粘贴为新图层；&lt;/li&gt;
&lt;li&gt;透明区域用经典的灰白棋盘格打底。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;12-选区工具7-个&#34;&gt;1.2 选区工具（7 个）&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;工具&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;魔棒&lt;/td&gt;
					&lt;td&gt;容差可调（默认 23），支持多连通区域一次选中&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;色彩范围&lt;/td&gt;
					&lt;td&gt;全图相似色选取&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;多边形套索&lt;/td&gt;
					&lt;td&gt;逐点勾勒&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;自由套索&lt;/td&gt;
					&lt;td&gt;鼠标轨迹勾勒&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;磁性套索&lt;/td&gt;
					&lt;td&gt;局部窗口内吸附边缘（完成度不高）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;贝塞尔选区&lt;/td&gt;
					&lt;td&gt;双击闭合时自动做曲线拟合，生成平滑选区&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;移动工具&lt;/td&gt;
					&lt;td&gt;把选区内容提成浮动层，拖动实时合成，松手落回底层&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;选区还支持：&lt;strong&gt;羽化&lt;/strong&gt;（可调半径）、反选、平移/缩放（AffineTransform）、以及标志性的&lt;strong&gt;蚂蚁线动画&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;13-滤镜24-个分-5-大类&#34;&gt;1.3 滤镜（24 个，分 5 大类）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;模糊&lt;/strong&gt;：均值平滑、高斯模糊（3×3 高斯核 &lt;code&gt;{1,2,1;2,4,2;1,2,1}/16&lt;/code&gt;）、中值滤波；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;锐化&lt;/strong&gt;：Sharpen / SharpenMore（拉普拉斯卷积核 &lt;code&gt;{0,-1,0;-1,5,-1;0,-1,0}&lt;/code&gt;）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;艺术&lt;/strong&gt;：曝光过度、冰晶、熔岩、暗调（平方衰减）、漫画等 8 种风格化效果；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;简单&lt;/strong&gt;：反色、怀旧（sepia 矩阵 &lt;code&gt;0.393/0.769/0.189...&lt;/code&gt;）、灰度；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;扭曲&lt;/strong&gt;：挤压、球面化、正/反旋转、正弦波浪、摩尔纹——扭曲类全部带&lt;strong&gt;多段滑块实时预览&lt;/strong&gt;，取消可还原。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;14-图像调整与文字&#34;&gt;1.4 图像调整与文字&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HSB 调整&lt;/strong&gt;：先把整图 RGB 转成 HSB 三维 float 数组，色相/饱和度/亮度三个滑块实时调节再转回 RGB；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对比度&lt;/strong&gt;调整；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文字工具&lt;/strong&gt;：点击图片任意位置就地出现透明输入框 + 字体选择面板，完成后生成为独立的文字图层。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;15-文件与工程&#34;&gt;1.5 文件与工程&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;打开 jpg / png / gif / bmp；&lt;/li&gt;
&lt;li&gt;自定义 &lt;strong&gt;&lt;code&gt;.jps&lt;/code&gt; 工程格式&lt;/strong&gt;（类似 PSD）：整个图层模型实现 &lt;code&gt;Serializable&lt;/code&gt;，用 &lt;code&gt;ObjectOutputStream&lt;/code&gt; 套 &lt;code&gt;GZIPOutputStream&lt;/code&gt; 序列化保存，可以随时回来继续编辑；&lt;/li&gt;
&lt;li&gt;复制 / 剪切 / 粘贴、导航缩放窗口、中文界面。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二架构总览&#34;&gt;二、架构总览&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[&amp;#34;JPhotoShop 入口&amp;lt;br/&amp;gt;EDT 中构建主窗口&amp;#34;] --&amp;gt; B[&amp;#34;JDesktopPane MDI 主框架&amp;#34;]
    B --&amp;gt; C[&amp;#34;EditFrame 编辑窗口&amp;lt;br/&amp;gt;每张图一个&amp;#34;]
    B --&amp;gt; D[&amp;#34;LayerControl 图层面板&amp;#34;]
    B --&amp;gt; E[&amp;#34;ViewSizeFrame 导航/缩放&amp;#34;]
    C --&amp;gt; F[&amp;#34;EditModel&amp;lt;br/&amp;gt;像素模型 + MemoryImageSource 渲染&amp;#34;]
    F --&amp;gt; G[&amp;#34;ImageModel&amp;lt;br/&amp;gt;LinkedList 图层栈&amp;#34;]
    G --&amp;gt; H[&amp;#34;ImagePaintLayer / TextPaintLayer / TempImageLayer&amp;#34;]
    H --&amp;gt; I[&amp;#34;LayerPlug 混合样式&amp;lt;br/&amp;gt;Alpha 合成&amp;#34;]
    C --&amp;gt; J[&amp;#34;ToolManager&amp;lt;br/&amp;gt;工具代理/状态切换&amp;#34;]
    J --&amp;gt; K[&amp;#34;Tool 抽象类&amp;lt;br/&amp;gt;魔棒/套索/贝塞尔/移动/文字...&amp;#34;]
    C --&amp;gt; L[&amp;#34;FilterManager&amp;lt;br/&amp;gt;properties + 反射装配&amp;#34;]
    L --&amp;gt; M[&amp;#34;PicFilter 滤镜即 Action&amp;#34;]
    M --&amp;gt; N[&amp;#34;Math 包&amp;lt;br/&amp;gt;CoverOp 卷积 / FeatherSide 羽化&amp;lt;br/&amp;gt;FindSide 轮廓追踪 / PointConnection 并查集&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;整体分四层：&lt;strong&gt;UI 层&lt;/strong&gt;（MDI 窗口与面板）、&lt;strong&gt;模型层&lt;/strong&gt;（图层栈与像素数据）、&lt;strong&gt;工具/滤镜层&lt;/strong&gt;（操作入口）、&lt;strong&gt;算法层&lt;/strong&gt;（&lt;code&gt;jphotoshop.Math&lt;/code&gt; 包）。工具和滤镜都通过 properties 配置 + 反射装配，下面详细说。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三技术亮点&#34;&gt;三、技术亮点&lt;/h2&gt;
&lt;h3 id=&#34;31-自绘像素管线int--memoryimagesource&#34;&gt;3.1 自绘像素管线：int[][] + MemoryImageSource&lt;/h3&gt;
&lt;p&gt;核心存储没有用 &lt;code&gt;BufferedImage&lt;/code&gt;，而是直接操作 &lt;strong&gt;&lt;code&gt;int[][]&lt;/code&gt; ARGB 二维数组&lt;/strong&gt;——所有算法（卷积、混合、羽化）都在裸数组上做，直观且没有额外封装开销。&lt;/p&gt;
&lt;p&gt;显示侧用了一个当年很典型的 Swing 高性能技巧：&lt;code&gt;MemoryImageSource&lt;/code&gt; + &lt;code&gt;setAnimated(true)&lt;/code&gt;，像素变化后调 &lt;code&gt;newPixels()&lt;/code&gt; &lt;strong&gt;增量刷新&lt;/strong&gt;屏幕图像，避免每次重绘都重建整个 Image 对象。编辑、拖选区、调滤镜滑块时的实时反馈都靠它撑着。&lt;/p&gt;
&lt;h3 id=&#34;32-链式图层合成缓存&#34;&gt;3.2 链式图层合成缓存&lt;/h3&gt;
&lt;p&gt;多图层合成是最频繁的操作，朴素实现是每次重绘把所有层从头合一遍。这里的做法是给每个图层挂一个&amp;quot;&lt;strong&gt;本层与下面所有层合成结果的缓存&lt;/strong&gt; &lt;code&gt;cacheData&lt;/code&gt;&amp;quot;，层与层之间用 &lt;code&gt;backLayer&lt;/code&gt; 引用连成链：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    BG[&amp;#34;背景层&amp;lt;br/&amp;gt;棋盘格&amp;#34;] --&amp;gt;|&amp;#34;合成&amp;#34;| L1[&amp;#34;图层1&amp;lt;br/&amp;gt;cacheData&amp;#34;]
    L1 --&amp;gt;|&amp;#34;合成&amp;#34;| L2[&amp;#34;图层2&amp;lt;br/&amp;gt;cacheData&amp;#34;]
    L2 --&amp;gt;|&amp;#34;合成&amp;#34;| L3[&amp;#34;图层3&amp;lt;br/&amp;gt;cacheData&amp;#34;]
    L3 --&amp;gt; OUT[&amp;#34;最终显示图像&amp;#34;]
    style L2 fill:#f96
    L2 -. &amp;#34;仅从此层向上重建&amp;#34; .-&amp;gt; L3&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;某一层像素变化时，&lt;strong&gt;只从该层向上依次 &lt;code&gt;reBuild&lt;/code&gt;&lt;/strong&gt;，下面的层缓存不动。层数多、图大时，这个收益非常明显。&lt;/p&gt;
&lt;h3 id=&#34;33-手写-alpha-混合&#34;&gt;3.3 手写 Alpha 混合&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;AlphaLayerStyle&lt;/code&gt; 实现了逐像素的经典混合公式 &lt;code&gt;C = α·Cf + (1-α)·Cb&lt;/code&gt;，并且处理了&lt;strong&gt;任意偏移（含负坐标裁剪）&lt;/strong&gt;——浮动选区拖到画布边缘外也能正确合成。羽化后的选区掩码是 short 型 alpha 权重，抠图粘贴时按权重衰减，边缘不会出现生硬的锯齿。&lt;/p&gt;
&lt;h3 id=&#34;34-魔棒一条完整的算法链&#34;&gt;3.4 魔棒：一条完整的算法链&lt;/h3&gt;
&lt;p&gt;魔棒是整个项目里算法含量最高的功能，完整链路分三步：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[&amp;#34;用户点击图片某点&amp;#34;] --&amp;gt; B[&amp;#34;Flood Fill 泛洪填充&amp;lt;br/&amp;gt;8邻域 + 容差判断&amp;lt;br/&amp;gt;ΔR+ΔG+ΔB ＜ range&amp;#34;]
    B --&amp;gt; C[&amp;#34;PointConnection 并查集&amp;lt;br/&amp;gt;id 数组 + sz 数组按秩合并&amp;lt;br/&amp;gt;分离多个互不连通的区域&amp;#34;]
    C --&amp;gt; D[&amp;#34;FindSide 8方向轮廓追踪&amp;lt;br/&amp;gt;沿区域边界走一圈&amp;lt;br/&amp;gt;每3步抽稀路径点&amp;#34;]
    D --&amp;gt; E[&amp;#34;GeneralPath 选区路径&amp;#34;]
    E --&amp;gt; F[&amp;#34;蚂蚁线动画&amp;lt;br/&amp;gt;Timer 500ms 切换两条相位差3的虚线&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;几个细节：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;泛洪填充用 &lt;code&gt;LinkedList&lt;/code&gt; 当队列，容差判断用曼哈顿距离（&lt;code&gt;|ΔR|+|ΔG|+|ΔB|&lt;/code&gt;），比欧氏距离省开方；&lt;/li&gt;
&lt;li&gt;一次点击可能选中多个互不连通的区域（比如图片里分散的几块蓝天），用&lt;strong&gt;并查集&lt;/strong&gt;把连通域分组，每个域独立生成轮廓；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;轮廓追踪&lt;/strong&gt;把面状的 boolean 掩码转成线状的路径点列，这一步直接决定蚂蚁线的平滑程度；&lt;/li&gt;
&lt;li&gt;蚂蚁线动画是两条相位差为 3 的虚线 &lt;code&gt;BasicStroke&lt;/code&gt; 用 500ms 的 &lt;code&gt;Timer&lt;/code&gt; 交替绘制——朴素但效果完全对味。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;35-通用卷积引擎-coverop&#34;&gt;3.5 通用卷积引擎 CoverOp&lt;/h3&gt;
&lt;p&gt;所有卷积类滤镜（模糊、锐化背后是同一个引擎 &lt;code&gt;CoverOp&lt;/code&gt;，提供三种重载：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;全图卷积；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;掩码限定区域卷积&lt;/strong&gt;——滤镜只在选区内生效，这是 PS 的基本体验；&lt;/li&gt;
&lt;li&gt;short alpha 掩码卷积——配合羽化选区做渐变处理。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;RGB 三通道分离卷积 + 边界 clamp，滤镜类只需要给一个卷积核就完事。新增一个模糊变体的成本是十几行代码。&lt;/p&gt;
&lt;h3 id=&#34;36-贝塞尔选区schneider-曲线拟合&#34;&gt;3.6 贝塞尔选区：Schneider 曲线拟合&lt;/h3&gt;
&lt;p&gt;自由手绘的选区轨迹是抖动的折线，直接当选区很难看。贝塞尔选区工具在双击闭合时调用 JHotDraw 的 &lt;code&gt;Bezier.fitCurve&lt;/code&gt;（Schneider 的经典拟合算法），把鼠标点列拟合成平滑的三次贝塞尔曲线段——这就是 PS 里&amp;quot;钢笔工具&amp;quot;的底层原理。&lt;/p&gt;
&lt;h3 id=&#34;37-声明式装配properties--反射的原始插件化&#34;&gt;3.7 声明式装配：properties + 反射的原始&amp;quot;插件化&amp;quot;&lt;/h3&gt;
&lt;p&gt;工具和滤镜都&lt;strong&gt;不写死在代码里&lt;/strong&gt;，而是从 properties 读类名、&lt;code&gt;Class.forName&lt;/code&gt; 反射实例化：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-properties&#34; data-lang=&#34;properties&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# resource/filters.properties&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;filters.Kinds&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;模糊,锐化,艺术效果,简单,扭曲&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;模糊&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;Smooth,GaussBlur,Median&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;加一个新滤镜 = 写一个类 + 改一行配置，菜单自动按分类分组生成。工具栏同理（&lt;code&gt;ToolManager.initTool&lt;/code&gt;）。项目里还专门有一个 &lt;code&gt;plug&lt;/code&gt; 包在探索真正的外部插件机制（虽然只是个雏形）。在 2007 年没有 OSGi、没有 IoC 框架依赖的纯 Swing 工程里，这是最务实的可扩展方案。&lt;/p&gt;
&lt;h3 id=&#34;38-工具架构proxy--state-的混合&#34;&gt;3.8 工具架构：Proxy + State 的混合&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Tool&lt;/code&gt; 抽象类把底层 MouseEvent 翻译成语义化回调（&lt;code&gt;click / pressed / dragged / release / rightClick / clickDouble / move&lt;/code&gt;），子类只关心语义。&lt;code&gt;ToolManager&lt;/code&gt; 本身也是 &lt;code&gt;Tool&lt;/code&gt; 的子类，作为&lt;strong&gt;代理&lt;/strong&gt;把事件转发给当前激活的工具——选中哪个工具，事件流就切到哪个状态机上。每个工具还可以自带 &lt;code&gt;getPlugPanel()&lt;/code&gt; 参数面板，选中时挂到选项栏上，和 PS 的交互完全一致。&lt;/p&gt;
&lt;h3 id=&#34;39-扭曲滤镜的预览-取消模式&#34;&gt;3.9 扭曲滤镜的&amp;quot;预览-取消&amp;quot;模式&lt;/h3&gt;
&lt;p&gt;扭曲类滤镜（球面、旋转、波浪……）都是坐标重映射：对每个目标像素做逆极坐标变换，找它在原图的取样点。为了支持滑块&lt;strong&gt;实时预览又能取消还原&lt;/strong&gt;，&lt;code&gt;ContortFilter&lt;/code&gt; 在打开对话框时先备份 &lt;code&gt;tempData&lt;/code&gt;，滑块每次变化都在备份基础上重算，点取消直接把备份拷回——一个廉价的 Memento 模式。配套的 &lt;code&gt;MultiSlider&lt;/code&gt; 多段滑块控件也是自绘的。&lt;/p&gt;
&lt;h3 id=&#34;310-滤镜即-action&#34;&gt;3.10 滤镜即 Action&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;PicFilter extends AbstractAction&lt;/code&gt;，滤镜本身就是可以挂进菜单/工具栏的 Action：&lt;code&gt;actionPerformed&lt;/code&gt; 里自动取当前窗口、当前图层、当前选区掩码，还处理了&lt;strong&gt;选区坐标系到图层坐标系的换算&lt;/strong&gt;（&lt;code&gt;start -= layerStart&lt;/code&gt;），执行完触发 &lt;code&gt;rePaintEdit&lt;/code&gt; 刷新。框架把脏活都干了，滤镜作者只需要关注像素算法。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四诚实的复盘留下了什么遗憾&#34;&gt;四、诚实的复盘：留下了什么遗憾&lt;/h2&gt;
&lt;p&gt;开源的代码就是当年的样子，包括未完成的部分，如实列出：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;撤销/重做是空架子&lt;/strong&gt;：&lt;code&gt;UndoRedoManager&lt;/code&gt; 的骨架搭好了，但 &lt;code&gt;propertyChange&lt;/code&gt; 三个分支全是空的——这是当时没啃下来的硬骨头，现在看应该用 Command 模式重做，而不是当初设想的 property 监听方案；&lt;/li&gt;
&lt;li&gt;历史面板、通道面板、外部插件机制都只有雏形；&lt;/li&gt;
&lt;li&gt;磁性套索完成度不高；&lt;/li&gt;
&lt;li&gt;若干小 bug，比如 &lt;code&gt;FindSide&lt;/code&gt; 里找最近点写成了找最远点（&lt;code&gt;if (temp &amp;gt; MinLength)&lt;/code&gt; 应为 &lt;code&gt;&amp;lt;&lt;/code&gt;）、&lt;code&gt;&amp;quot;undo&amp;quot;.endsWith(...)&lt;/code&gt; 应为 &lt;code&gt;startsWith&lt;/code&gt;、几处类名 typo（&lt;code&gt;WaveFiler&lt;/code&gt;、&lt;code&gt;getSize&lt;/code&gt; 拼写）——权当留给读者的&amp;quot;考古彩蛋&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五写在最后&#34;&gt;五、写在最后&lt;/h2&gt;
&lt;p&gt;写 jphotoshop 的半年收获远超预期：并查集、轮廓追踪、卷积、贝塞尔拟合这些算法不再是教科书上的名词，而是要亲手调到能跑的东西；合成缓存、声明式装配、Action 抽象这些设计，也都是被真实需求逼出来的。如果你也想扎实练一次&amp;quot;算法 + 工程&amp;quot;的综合功，手写一个图像编辑器是我能想到的最好题目之一。&lt;/p&gt;
&lt;p&gt;源码在 &lt;a href=&#34;https://gitee.com/soulnew/jphotoshop&#34;&gt;https://gitee.com/soulnew/jphotoshop&lt;/a&gt;，欢迎 Star、Fork，更欢迎把那个空着的 &lt;code&gt;UndoRedoManager&lt;/code&gt; 补上。&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
