├── .gitattributes
├── .gitignore
├── License.txt
├── PSP format specs
├── PSP File Format Specification 10.pdf
├── psp5spec.pdf
├── psp6spec.doc
├── psp7spec.doc
└── psp8spec.doc
├── Readme.md
├── Test images
├── Flag Before.pspimage
├── Marble head.PspImage
├── Sunset_PSP10-CreateInfo.pspimage
├── Sunset_PSP10.pspimage
├── Sunset_PSP11.pspimage
├── Sunset_PSP12.pspimage
├── Sunset_PSP9.pspimage
├── Sunset_X3.pspimage
├── Sunset_X6.pspimage
├── psp9.pspimage
└── th_goldhill.pspimage
└── src
├── .editorconfig
├── CompressionFormats.cs
├── FileVersion.cs
├── FodyWeavers.xml
├── IO
├── BinaryWriterEx.cs
├── BlockLengthWriter.cs
├── EndianBinaryReader.cs
├── Endianess.cs
└── StringReadOptions.cs
├── IgnoredWords.dic
├── PSPFileType.cs
├── PSPFileTypeFactory.cs
├── PSPSections
├── ChannelSubBlock.cs
├── ColorPaletteBlock.cs
├── CompositeImageBlock.cs
├── CreatorBlock.DebugView.cs
├── CreatorBlock.cs
├── CreatorBlockSerialization.cs
├── CreatorBlockSerializedData.cs
├── ExtendedDataBlock.cs
├── FileHeader.cs
├── GeneralImageAttributes.cs
├── LayerBlock.cs
├── PSPConstants.cs
├── PSPEnums.cs
├── PSPFile.cs
├── PSPUtil.cs
├── RGBQUAD.cs
├── RLE.cs
└── ThumbnailBlock.cs
├── PaintShopProFiletype.csproj
├── PaintShopProFiletype.sln
├── PluginSupportInfo.cs
└── Properties
├── AssemblyInfo.cs
├── Resources.Designer.cs
└── Resources.resx
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # MSTest test Results
33 | [Tt]est[Rr]esult*/
34 | [Bb]uild[Ll]og.*
35 |
36 | # NUNIT
37 | *.VisualState.xml
38 | TestResult.xml
39 |
40 | # Build Results of an ATL Project
41 | [Dd]ebugPS/
42 | [Rr]eleasePS/
43 | dlldata.c
44 |
45 | # .NET Core
46 | project.lock.json
47 | project.fragment.lock.json
48 | artifacts/
49 | **/Properties/launchSettings.json
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # Visual Studio code coverage results
117 | *.coverage
118 | *.coveragexml
119 |
120 | # NCrunch
121 | _NCrunch_*
122 | .*crunch*.local.xml
123 | nCrunchTemp_*
124 |
125 | # MightyMoose
126 | *.mm.*
127 | AutoTest.Net/
128 |
129 | # Web workbench (sass)
130 | .sass-cache/
131 |
132 | # Installshield output folder
133 | [Ee]xpress/
134 |
135 | # DocProject is a documentation generator add-in
136 | DocProject/buildhelp/
137 | DocProject/Help/*.HxT
138 | DocProject/Help/*.HxC
139 | DocProject/Help/*.hhc
140 | DocProject/Help/*.hhk
141 | DocProject/Help/*.hhp
142 | DocProject/Help/Html2
143 | DocProject/Help/html
144 |
145 | # Click-Once directory
146 | publish/
147 |
148 | # Publish Web Output
149 | *.[Pp]ublish.xml
150 | *.azurePubxml
151 | # TODO: Comment the next line if you want to checkin your web deploy settings
152 | # but database connection strings (with potential passwords) will be unencrypted
153 | *.pubxml
154 | *.publishproj
155 |
156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
157 | # checkin your Azure Web App publish settings, but sensitive information contained
158 | # in these scripts will be unencrypted
159 | PublishScripts/
160 |
161 | # NuGet Packages
162 | *.nupkg
163 | # The packages folder can be ignored because of Package Restore
164 | **/packages/*
165 | # except build/, which is used as an MSBuild target.
166 | !**/packages/build/
167 | # Uncomment if necessary however generally it will be regenerated when needed
168 | #!**/packages/repositories.config
169 | # NuGet v3's project.json files produces more ignorable files
170 | *.nuget.props
171 | *.nuget.targets
172 |
173 | # Microsoft Azure Build Output
174 | csx/
175 | *.build.csdef
176 |
177 | # Microsoft Azure Emulator
178 | ecf/
179 | rcf/
180 |
181 | # Windows Store app package directories and files
182 | AppPackages/
183 | BundleArtifacts/
184 | Package.StoreAssociation.xml
185 | _pkginfo.txt
186 |
187 | # Visual Studio cache files
188 | # files ending in .cache can be ignored
189 | *.[Cc]ache
190 | # but keep track of directories ending in .cache
191 | !*.[Cc]ache/
192 |
193 | # Others
194 | ClientBin/
195 | ~$*
196 | *~
197 | *.dbmdl
198 | *.dbproj.schemaview
199 | *.jfm
200 | *.pfx
201 | *.publishsettings
202 | orleans.codegen.cs
203 |
204 | # Since there are multiple workflows, uncomment next line to ignore bower_components
205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
206 | #bower_components/
207 |
208 | # RIA/Silverlight projects
209 | Generated_Code/
210 |
211 | # Backup & report files from converting an old project file
212 | # to a newer Visual Studio version. Backup files are not needed,
213 | # because we have git ;-)
214 | _UpgradeReport_Files/
215 | Backup*/
216 | UpgradeLog*.XML
217 | UpgradeLog*.htm
218 |
219 | # SQL Server files
220 | *.mdf
221 | *.ldf
222 | *.ndf
223 |
224 | # Business Intelligence projects
225 | *.rdl.data
226 | *.bim.layout
227 | *.bim_*.settings
228 |
229 | # Microsoft Fakes
230 | FakesAssemblies/
231 |
232 | # GhostDoc plugin setting file
233 | *.GhostDoc.xml
234 |
235 | # Node.js Tools for Visual Studio
236 | .ntvs_analysis.dat
237 | node_modules/
238 |
239 | # Typescript v1 declaration files
240 | typings/
241 |
242 | # Visual Studio 6 build log
243 | *.plg
244 |
245 | # Visual Studio 6 workspace options file
246 | *.opt
247 |
248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
249 | *.vbw
250 |
251 | # Visual Studio LightSwitch build output
252 | **/*.HTMLClient/GeneratedArtifacts
253 | **/*.DesktopClient/GeneratedArtifacts
254 | **/*.DesktopClient/ModelManifest.xml
255 | **/*.Server/GeneratedArtifacts
256 | **/*.Server/ModelManifest.xml
257 | _Pvt_Extensions
258 |
259 | # Paket dependency manager
260 | .paket/paket.exe
261 | paket-files/
262 |
263 | # FAKE - F# Make
264 | .fake/
265 |
266 | # JetBrains Rider
267 | .idea/
268 | *.sln.iml
269 |
270 | # CodeRush
271 | .cr/
272 |
273 | # Python Tools for Visual Studio (PTVS)
274 | __pycache__/
275 | *.pyc
276 |
277 | # Cake - Uncomment if you are using it
278 | # tools/**
279 | # !tools/packages.config
280 |
281 | # Telerik's JustMock configuration file
282 | *.jmconfig
283 |
284 | # BizTalk build output
285 | *.btp.cs
286 | *.btm.cs
287 | *.odx.cs
288 | *.xsd.cs
289 |
290 | # Local repository
291 |
292 | FodyWeavers.xsd
293 |
--------------------------------------------------------------------------------
/License.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
24 |
25 | Portions of the software has been adapted from Photoshop PSD FileType Plugin for Paint.NET:
26 | http://psdplugin.codeplex.com/
27 |
28 | These portions are provided under the MIT License:
29 | http://opensource.org/licenses/MIT
30 |
31 | ----
32 |
33 | Copyright (c) 2006-2007 Frank Blumenberg
34 | Copyright (c) 2010-2013 Tao Yue
35 |
36 | MIT License: http://www.opensource.org/licenses/mit-license.php
37 |
38 | Permission is hereby granted, free of charge, to any person obtaining a copy
39 | of this software and associated documentation files (the "Software"), to deal
40 | in the Software without restriction, including without limitation the rights
41 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
42 | copies of the Software, and to permit persons to whom the Software is
43 | furnished to do so, subject to the following conditions:
44 |
45 | The above copyright notice and this permission notice shall be included in all
46 | copies or substantial portions of the Software.
47 |
48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
49 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
50 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
51 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
52 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
53 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
54 | THE SOFTWARE.
55 |
56 |
57 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
58 |
59 | Portions of this software has been adapted from Paint.NET 3.36
60 | http://www.getpaint.net
61 |
62 | These portions are provided under the following license:
63 |
64 | Paint.NET
65 | Copyright (C) dotPDN LLC, Rick Brewster, Chris Crosetto, Tom Jackson, Michael Kelsey, Brandon Ortiz, Craig Taylor, Chris Trevino, and Luke Walker.
66 | Portions Copyright (C) Microsoft Corporation. All Rights Reserved.
67 |
68 | This software is licensed as per the MIT License below, but with three (3) exceptions:
69 |
70 | * Exception 1: The Paint.NET logo and icon artwork are Copyright (C) Rick Brewster. They are covered by the Creative Commons Attribution-NonCommercial-NoDerivs 2.5 license which is detailed here: http://creativecommons.org/licenses/by-nc-nd/2.5/ . However, permission is granted to use the logo and icon artwork in ways that directly discuss or promote Paint.NET (e.g. blog and news posts about Paint.NET, "Made with Paint.NET" watermarks or insets).
71 |
72 | * Exception 2: Paint.NET makes use of certain text and graphic resources that it comes with (e.g., toolbar icon graphics, text for menu items and the status bar). These are collectively referred to as "resource assets" and are defined to include the contents of files installed by Paint.NET, or included in its source code distribution, that have a .RESOURCES, .RESX, or .PNG file extension. This also includes embedded resource files within the PaintDotNet.Resources.dll installed file. These "resource assets" are covered by the Creative Commons Attribution-NonCommercial-NoDerivs 2.5 license which is detailed here: http://creativecommons.org/licenses/by-nc-nd/2.5/ . However, permission is granted to create and distribute derivative works of the "resource assets" for the sole purpose of providing a translation to a language other than English. Some "resource assets" are included in unmodified form from external icon or image libraries and are still covered by their original, respective licenses (e.g., "Silk", "Visual Studio 2005 Image Library").
73 |
74 | * Exception 3: Although the Paint.NET source code distribution includes the GPC source code, use of the GPC code in any other commercial application is not permitted without a GPC Commercial Use Licence from The University of Manchester. For more information, please refer to the GPC website at: http://www.cs.man.ac.uk/~toby/alan/software/
75 |
76 | MIT License: http://www.opensource.org/licenses/mit-license.php
77 |
78 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
79 |
80 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
81 |
82 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
83 |
84 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
85 |
86 | .NET Community Toolkit
87 | https://github.com/CommunityToolkit/dotnet
88 |
89 | ----
90 |
91 | Copyright © .NET Foundation and Contributors
92 |
93 | All rights reserved.
94 | MIT License (MIT)
95 |
96 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
97 |
98 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
99 |
100 | THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/PSP format specs/PSP File Format Specification 10.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xC0000054/pdn-pspformat/c0f6bbd8dee0e432f4332e5ba35eea817226fe62/PSP format specs/PSP File Format Specification 10.pdf
--------------------------------------------------------------------------------
/PSP format specs/psp5spec.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xC0000054/pdn-pspformat/c0f6bbd8dee0e432f4332e5ba35eea817226fe62/PSP format specs/psp5spec.pdf
--------------------------------------------------------------------------------
/PSP format specs/psp6spec.doc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xC0000054/pdn-pspformat/c0f6bbd8dee0e432f4332e5ba35eea817226fe62/PSP format specs/psp6spec.doc
--------------------------------------------------------------------------------
/PSP format specs/psp7spec.doc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xC0000054/pdn-pspformat/c0f6bbd8dee0e432f4332e5ba35eea817226fe62/PSP format specs/psp7spec.doc
--------------------------------------------------------------------------------
/PSP format specs/psp8spec.doc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xC0000054/pdn-pspformat/c0f6bbd8dee0e432f4332e5ba35eea817226fe62/PSP format specs/psp8spec.doc
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # pdn-pspformat
2 |
3 | A [Paint.NET](http://www.getpaint.net) FileType plugin that loads and saves Paint Shop Pro® images.
4 |
5 | ## How to install the plugin
6 |
7 | 1. Close Paint.NET.
8 | 2. Place PaintShopProFiletype.dll in the Paint.NET FileTypes folder which is usually located in one the following locations depending on the Paint.NET version you have installed.
9 |
10 | Paint.NET Version | FileTypes Folder Location
11 | --------|----------
12 | Classic | C:\Program Files\Paint.NET\FileTypes
13 | Microsoft Store | Documents\paint.net App Files\FileTypes
14 |
15 | 3. Restart Paint.NET.
16 |
17 | ## License
18 |
19 | This project is licensed under the terms of the MIT License.
20 | See [License.txt](License.txt) for more information.
--------------------------------------------------------------------------------
/Test images/Flag Before.pspimage:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xC0000054/pdn-pspformat/c0f6bbd8dee0e432f4332e5ba35eea817226fe62/Test images/Flag Before.pspimage
--------------------------------------------------------------------------------
/Test images/Marble head.PspImage:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xC0000054/pdn-pspformat/c0f6bbd8dee0e432f4332e5ba35eea817226fe62/Test images/Marble head.PspImage
--------------------------------------------------------------------------------
/Test images/Sunset_PSP10-CreateInfo.pspimage:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xC0000054/pdn-pspformat/c0f6bbd8dee0e432f4332e5ba35eea817226fe62/Test images/Sunset_PSP10-CreateInfo.pspimage
--------------------------------------------------------------------------------
/Test images/Sunset_PSP10.pspimage:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xC0000054/pdn-pspformat/c0f6bbd8dee0e432f4332e5ba35eea817226fe62/Test images/Sunset_PSP10.pspimage
--------------------------------------------------------------------------------
/Test images/Sunset_PSP11.pspimage:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xC0000054/pdn-pspformat/c0f6bbd8dee0e432f4332e5ba35eea817226fe62/Test images/Sunset_PSP11.pspimage
--------------------------------------------------------------------------------
/Test images/Sunset_PSP12.pspimage:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xC0000054/pdn-pspformat/c0f6bbd8dee0e432f4332e5ba35eea817226fe62/Test images/Sunset_PSP12.pspimage
--------------------------------------------------------------------------------
/Test images/Sunset_PSP9.pspimage:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xC0000054/pdn-pspformat/c0f6bbd8dee0e432f4332e5ba35eea817226fe62/Test images/Sunset_PSP9.pspimage
--------------------------------------------------------------------------------
/Test images/Sunset_X3.pspimage:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xC0000054/pdn-pspformat/c0f6bbd8dee0e432f4332e5ba35eea817226fe62/Test images/Sunset_X3.pspimage
--------------------------------------------------------------------------------
/Test images/Sunset_X6.pspimage:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xC0000054/pdn-pspformat/c0f6bbd8dee0e432f4332e5ba35eea817226fe62/Test images/Sunset_X6.pspimage
--------------------------------------------------------------------------------
/Test images/psp9.pspimage:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xC0000054/pdn-pspformat/c0f6bbd8dee0e432f4332e5ba35eea817226fe62/Test images/psp9.pspimage
--------------------------------------------------------------------------------
/Test images/th_goldhill.pspimage:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xC0000054/pdn-pspformat/c0f6bbd8dee0e432f4332e5ba35eea817226fe62/Test images/th_goldhill.pspimage
--------------------------------------------------------------------------------
/src/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | end_of_line = crlf
8 |
9 | vsspell_section_id = 30a86b75973f41d3b6fe30b8f7950859
10 | vsspell_ignored_words_30a86b75973f41d3b6fe30b8f7950859 = File:IgnoredWords.dic
11 |
12 | [*.cs]
13 | indent_style = space
14 | indent_size = 4
15 | trim_trailing_whitespace = true
16 |
17 | # IDE0090: Simplify 'new' expression
18 | csharp_style_implicit_object_creation_when_type_is_apparent = false
19 |
20 | # IDE0057: Use range operator
21 | csharp_style_prefer_range_operator = false
22 |
23 | # Newline settings
24 | csharp_new_line_before_open_brace = all
25 | csharp_new_line_before_else = true
26 | csharp_new_line_before_catch = true
27 | csharp_new_line_before_finally = true
28 | csharp_new_line_before_members_in_object_initializers = true
29 | csharp_new_line_before_members_in_anonymous_types = true
30 | csharp_new_line_between_query_expression_clauses = true
31 |
32 | # Expression-bodied member settings
33 | csharp_style_expression_bodied_methods = false : silent
34 | csharp_style_expression_bodied_constructors = false : silent
35 | csharp_style_expression_bodied_operators = false : silent
36 | csharp_style_expression_bodied_properties = true : silent
37 | csharp_style_expression_bodied_indexers = true : silent
38 | csharp_style_expression_bodied_accessors = true : silent
39 |
40 | # Code block settings
41 | csharp_prefer_braces = true : warning
42 |
43 | # "This." qualifier settings
44 | dotnet_style_qualification_for_field = true : suggestion
45 | dotnet_style_qualification_for_property = true : suggestion
46 | dotnet_style_qualification_for_method = false : suggestion
47 | dotnet_style_qualification_for_event = true : suggestion
48 |
--------------------------------------------------------------------------------
/src/CompressionFormats.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | namespace PaintShopProFiletype
13 | {
14 | internal enum CompressionFormats
15 | {
16 | None,
17 | LZ77
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/FileVersion.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | namespace PaintShopProFiletype
13 | {
14 | internal enum FileVersion
15 | {
16 | Version5 = 3,
17 | Version6,
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/FodyWeavers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/IO/BinaryWriterEx.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using System.IO;
13 |
14 | namespace PaintShopProFiletype.IO
15 | {
16 | internal class BinaryWriterEx : BinaryWriter
17 | {
18 | private readonly bool ownStream;
19 |
20 | public BinaryWriterEx(Stream stream, bool ownStream) : base(stream)
21 | {
22 | this.ownStream = ownStream;
23 | }
24 |
25 | protected override void Dispose(bool disposing)
26 | {
27 | if (disposing && this.ownStream)
28 | {
29 | base.OutStream.Dispose();
30 | }
31 | }
32 |
33 | public void Write(System.Drawing.Rectangle rect)
34 | {
35 | Write(rect.Left);
36 | Write(rect.Top);
37 | Write(rect.Right);
38 | Write(rect.Bottom);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/IO/BlockLengthWriter.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | // Portions of this file has been adapted from:
13 | /////////////////////////////////////////////////////////////////////////////////
14 | //
15 | // Photoshop PSD FileType Plugin for Paint.NET
16 | // http://psdplugin.codeplex.com/
17 | //
18 | // This software is provided under the MIT License:
19 | // Copyright (c) 2006-2007 Frank Blumenberg
20 | // Copyright (c) 2010-2013 Tao Yue
21 | //
22 | // See LICENSE.txt for complete licensing and attribution information.
23 | //
24 | /////////////////////////////////////////////////////////////////////////////////
25 |
26 | using System;
27 | using System.IO;
28 |
29 | namespace PaintShopProFiletype.IO
30 | {
31 | internal class BlockLengthWriter : IDisposable
32 | {
33 | private readonly BinaryWriter writer;
34 | private readonly long startPos;
35 | private readonly long lengthPos;
36 | private bool disposed;
37 |
38 | public BlockLengthWriter(BinaryWriter binaryWriter)
39 | {
40 | this.writer = binaryWriter;
41 | this.lengthPos = this.writer.BaseStream.Position;
42 | this.writer.Write(0xBADF00DU);
43 | this.startPos = this.writer.BaseStream.Position;
44 | this.disposed = false;
45 | }
46 |
47 | public void Dispose()
48 | {
49 | if (!this.disposed)
50 | {
51 | long end = this.writer.BaseStream.Position;
52 | long sectionLength = end - this.startPos;
53 |
54 | this.writer.BaseStream.Position = this.lengthPos;
55 | this.writer.Write((uint)sectionLength);
56 |
57 | this.writer.BaseStream.Position = end;
58 |
59 | this.disposed = true;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/IO/EndianBinaryReader.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using System;
13 | using System.Buffers.Binary;
14 | using System.IO;
15 | using System.Runtime.CompilerServices;
16 |
17 | namespace PaintShopProFiletype.IO
18 | {
19 | // Adapted from 'Problem and Solution: The Terrible Inefficiency of FileStream and BinaryReader'
20 | // https://jacksondunstan.com/articles/3568
21 |
22 | internal sealed class EndianBinaryReader : PaintDotNet.Disposable
23 | {
24 | private const int MaxBufferSize = 4096;
25 |
26 | private static ReadOnlySpan AsciiWhiteSpaceChars => new byte[]
27 | {
28 | 0x09, // horizontal tab
29 | 0x10, // line feed
30 | 0x11, // vertical tab
31 | 0x12, // form feed
32 | 0x13, // carriage return
33 | 0x20, // space
34 | };
35 |
36 | #pragma warning disable IDE0032 // Use auto property
37 | private int readOffset;
38 | private int readLength;
39 |
40 | private readonly Stream stream;
41 | private readonly byte[] buffer;
42 | private readonly int bufferSize;
43 | private readonly Endianess endianess;
44 | private readonly bool leaveOpen;
45 | #pragma warning restore IDE0032 // Use auto property
46 |
47 | ///
48 | /// Initializes a new instance of the class.
49 | ///
50 | /// The stream.
51 | /// The byte order of the stream.
52 | /// is null.
53 | public EndianBinaryReader(Stream stream, Endianess byteOrder) : this(stream, byteOrder, false)
54 | {
55 | }
56 |
57 | ///
58 | /// Initializes a new instance of the class.
59 | ///
60 | /// The stream.
61 | /// The byte order of the stream.
62 | ///
63 | /// to leave the stream open after the EndianBinaryReader is disposed; otherwise,
64 | ///
65 | /// is null.
66 | public EndianBinaryReader(Stream stream, Endianess byteOrder, bool leaveOpen)
67 | {
68 | this.stream = stream ?? throw new ArgumentNullException(nameof(stream));
69 | this.bufferSize = (int)Math.Min(stream.Length, MaxBufferSize);
70 | this.buffer = new byte[this.bufferSize];
71 | this.endianess = byteOrder;
72 | this.leaveOpen = leaveOpen;
73 |
74 | this.readOffset = 0;
75 | this.readLength = 0;
76 | }
77 |
78 | public Endianess Endianess => this.endianess;
79 |
80 | ///
81 | /// Gets the length of the stream.
82 | ///
83 | ///
84 | /// The length of the stream.
85 | ///
86 | /// The object has been disposed.
87 | public long Length
88 | {
89 | get
90 | {
91 | VerifyNotDisposed();
92 |
93 | return this.stream.Length;
94 | }
95 | }
96 |
97 | ///
98 | /// Gets or sets the position in the stream.
99 | ///
100 | ///
101 | /// The position in the stream.
102 | ///
103 | /// value is negative.
104 | /// The object has been disposed.
105 | public long Position
106 | {
107 | get
108 | {
109 | VerifyNotDisposed();
110 |
111 | return this.stream.Position - this.readLength + this.readOffset;
112 | }
113 | set
114 | {
115 | if (value < 0)
116 | {
117 | throw new ArgumentOutOfRangeException(nameof(value));
118 | }
119 | VerifyNotDisposed();
120 |
121 | long current = this.Position;
122 |
123 | if (value != current)
124 | {
125 | long bufferStartOffset = current - this.readOffset;
126 | long bufferEndOffset = bufferStartOffset + this.readLength;
127 |
128 | // Avoid reading from the stream if the offset is within the current buffer.
129 | if (value >= bufferStartOffset && value <= bufferEndOffset)
130 | {
131 | this.readOffset = (int)(value - bufferStartOffset);
132 | }
133 | else
134 | {
135 | // Invalidate the existing buffer.
136 | this.readOffset = 0;
137 | this.readLength = 0;
138 | this.stream.Seek(value, SeekOrigin.Begin);
139 | }
140 | }
141 | }
142 | }
143 |
144 | ///
145 | /// Reads the specified number of bytes from the stream, starting from a specified point in the byte array.
146 | ///
147 | /// The bytes.
148 | /// The starting offset in the array.
149 | /// The count.
150 | /// The number of bytes read from the stream.
151 | /// is null.
152 | /// is negative.
153 | /// The end of the stream has been reached.
154 | /// The object has been disposed.
155 | public int Read(byte[] bytes, int offset, int count)
156 | {
157 | if (bytes == null)
158 | {
159 | throw new ArgumentNullException(nameof(bytes));
160 | }
161 | if (count < 0)
162 | {
163 | throw new ArgumentOutOfRangeException(nameof(count));
164 | }
165 | VerifyNotDisposed();
166 |
167 | if (count == 0)
168 | {
169 | return 0;
170 | }
171 |
172 | if ((this.readOffset + count) <= this.readLength)
173 | {
174 | Buffer.BlockCopy(this.buffer, this.readOffset, bytes, offset, count);
175 | this.readOffset += count;
176 |
177 | return count;
178 | }
179 | else
180 | {
181 | // Ensure that any bytes at the end of the current buffer are included.
182 | int bytesUnread = this.readLength - this.readOffset;
183 |
184 | if (bytesUnread > 0)
185 | {
186 | Buffer.BlockCopy(this.buffer, this.readOffset, bytes, offset, bytesUnread);
187 | }
188 |
189 | // Invalidate the existing buffer.
190 | this.readOffset = 0;
191 | this.readLength = 0;
192 |
193 | int totalBytesRead = bytesUnread;
194 |
195 | totalBytesRead += this.stream.Read(bytes, offset + bytesUnread, count - bytesUnread);
196 |
197 | return totalBytesRead;
198 | }
199 | }
200 |
201 | ///
202 | /// Reads the next byte from the current stream.
203 | ///
204 | /// The next byte read from the current stream.
205 | /// The end of the stream has been reached.
206 | /// The object has been disposed.
207 | public byte ReadByte()
208 | {
209 | VerifyNotDisposed();
210 |
211 | EnsureBuffer(sizeof(byte));
212 |
213 | byte val = this.buffer[this.readOffset];
214 | this.readOffset += sizeof(byte);
215 |
216 | return val;
217 | }
218 |
219 | ///
220 | /// Reads the specified number of bytes from the stream.
221 | ///
222 | /// The number of bytes to read..
223 | /// An array containing the specified bytes.
224 | /// is negative.
225 | /// The end of the stream has been reached.
226 | /// The object has been disposed.
227 | public byte[] ReadBytes(int count)
228 | {
229 | if (count < 0)
230 | {
231 | throw new ArgumentOutOfRangeException(nameof(count));
232 | }
233 | VerifyNotDisposed();
234 |
235 | if (count == 0)
236 | {
237 | return Array.Empty();
238 | }
239 |
240 | byte[] bytes = new byte[count];
241 |
242 | if ((this.readOffset + count) <= this.readLength)
243 | {
244 | Buffer.BlockCopy(this.buffer, this.readOffset, bytes, 0, count);
245 | this.readOffset += count;
246 | }
247 | else
248 | {
249 | // Ensure that any bytes at the end of the current buffer are included.
250 | int bytesUnread = this.readLength - this.readOffset;
251 |
252 | if (bytesUnread > 0)
253 | {
254 | Buffer.BlockCopy(this.buffer, this.readOffset, bytes, 0, bytesUnread);
255 | }
256 |
257 | int numBytesToRead = count - bytesUnread;
258 | int numBytesRead = bytesUnread;
259 | do
260 | {
261 | int n = this.stream.Read(bytes, numBytesRead, numBytesToRead);
262 |
263 | if (n == 0)
264 | {
265 | throw new EndOfStreamException();
266 | }
267 |
268 | numBytesRead += n;
269 | numBytesToRead -= n;
270 |
271 | } while (numBytesToRead > 0);
272 |
273 | // Invalidate the existing buffer.
274 | this.readOffset = 0;
275 | this.readLength = 0;
276 | }
277 |
278 | return bytes;
279 | }
280 |
281 | ///
282 | /// Reads a 8-byte floating point value.
283 | ///
284 | /// The 8-byte floating point value.
285 | /// The end of the stream has been reached.
286 | /// The object has been disposed.
287 | public unsafe double ReadDouble()
288 | {
289 | ulong temp = ReadUInt64();
290 |
291 | return *(double*)&temp;
292 | }
293 |
294 | ///
295 | /// Reads the specified number of bytes from the stream.
296 | ///
297 | /// The destination buffer.
298 | /// The end of the stream has been reached.
299 | /// The object has been disposed.
300 | public void ReadExactly(Span buffer)
301 | {
302 | VerifyNotDisposed();
303 |
304 | Span destination = buffer;
305 |
306 | while (destination.Length > 0)
307 | {
308 | int bytesRead = ReadInternal(destination);
309 |
310 | if (bytesRead == 0)
311 | {
312 | throw new EndOfStreamException();
313 | }
314 |
315 | destination = destination.Slice(bytesRead);
316 | }
317 | }
318 |
319 | ///
320 | /// Reads a 2-byte signed integer.
321 | ///
322 | /// The 2-byte signed integer.
323 | /// The end of the stream has been reached.
324 | /// The object has been disposed.
325 | public short ReadInt16()
326 | {
327 | return (short)ReadUInt16();
328 | }
329 |
330 | ///
331 | /// Reads a 2-byte unsigned integer.
332 | ///
333 | /// The 2-byte unsigned integer.
334 | /// The end of the stream has been reached.
335 | /// The object has been disposed.
336 | public ushort ReadUInt16()
337 | {
338 | VerifyNotDisposed();
339 |
340 | EnsureBuffer(sizeof(ushort));
341 |
342 | ushort value = Unsafe.ReadUnaligned(ref this.buffer[this.readOffset]);
343 |
344 | switch (this.endianess)
345 | {
346 | case Endianess.Big:
347 | if (BitConverter.IsLittleEndian)
348 | {
349 | value = BinaryPrimitives.ReverseEndianness(value);
350 | }
351 | break;
352 | case Endianess.Little:
353 | if (!BitConverter.IsLittleEndian)
354 | {
355 | value = BinaryPrimitives.ReverseEndianness(value);
356 | }
357 | break;
358 | default:
359 | throw new InvalidOperationException("Unsupported byte order: " + this.endianess.ToString());
360 | }
361 |
362 | this.readOffset += sizeof(ushort);
363 |
364 | return value;
365 | }
366 |
367 | ///
368 | /// Reads a 4-byte signed integer.
369 | ///
370 | /// The 4-byte signed integer.
371 | /// The end of the stream has been reached.
372 | /// The object has been disposed.
373 | public int ReadInt32()
374 | {
375 | return (int)ReadUInt32();
376 | }
377 |
378 | ///
379 | /// Reads a 4-byte unsigned integer.
380 | ///
381 | /// The 4-byte unsigned integer.
382 | /// The end of the stream has been reached.
383 | /// The object has been disposed.
384 | public uint ReadUInt32()
385 | {
386 | VerifyNotDisposed();
387 |
388 | EnsureBuffer(sizeof(uint));
389 |
390 | uint value = Unsafe.ReadUnaligned(ref this.buffer[this.readOffset]);
391 |
392 | switch (this.endianess)
393 | {
394 | case Endianess.Big:
395 | if (BitConverter.IsLittleEndian)
396 | {
397 | value = BinaryPrimitives.ReverseEndianness(value);
398 | }
399 | break;
400 | case Endianess.Little:
401 | if (!BitConverter.IsLittleEndian)
402 | {
403 | value = BinaryPrimitives.ReverseEndianness(value);
404 | }
405 | break;
406 | default:
407 | throw new InvalidOperationException("Unsupported byte order: " + this.endianess.ToString());
408 | }
409 |
410 | this.readOffset += sizeof(uint);
411 |
412 | return value;
413 | }
414 |
415 | ///
416 | /// Reads a 4-byte floating point value.
417 | ///
418 | /// The 4-byte floating point value.
419 | /// The end of the stream has been reached.
420 | /// The object has been disposed.
421 | public unsafe float ReadSingle()
422 | {
423 | uint temp = ReadUInt32();
424 |
425 | return *(float*)&temp;
426 | }
427 |
428 | ///
429 | /// Reads a 8-byte signed integer.
430 | ///
431 | /// The 8-byte signed integer.
432 | /// The end of the stream has been reached.
433 | /// The object has been disposed.
434 | public long ReadInt64()
435 | {
436 | return (long)ReadUInt64();
437 | }
438 |
439 | ///
440 | /// Reads a 8-byte unsigned integer.
441 | ///
442 | /// The 8-byte unsigned integer.
443 | /// The end of the stream has been reached.
444 | /// The object has been disposed.
445 | public ulong ReadUInt64()
446 | {
447 | VerifyNotDisposed();
448 |
449 | EnsureBuffer(sizeof(ulong));
450 |
451 | ulong value = Unsafe.ReadUnaligned(ref this.buffer[this.readOffset]);
452 |
453 | switch (this.endianess)
454 | {
455 | case Endianess.Big:
456 | if (BitConverter.IsLittleEndian)
457 | {
458 | value = BinaryPrimitives.ReverseEndianness(value);
459 | }
460 | break;
461 | case Endianess.Little:
462 | if (!BitConverter.IsLittleEndian)
463 | {
464 | value = BinaryPrimitives.ReverseEndianness(value);
465 | }
466 | break;
467 | default:
468 | throw new InvalidOperationException("Unsupported byte order: " + this.endianess.ToString());
469 | }
470 |
471 | this.readOffset += sizeof(ulong);
472 |
473 | return value;
474 | }
475 |
476 | ///
477 | /// Reads an ASCII string from the stream.
478 | ///
479 | /// The length of the string.
480 | /// The string read options.
481 | /// The string.
482 | /// is negative.
483 | /// The end of the stream has been reached.
484 | /// The object has been disposed.
485 | public string ReadAsciiString(int length, StringReadOptions options)
486 | {
487 | if (length < 0)
488 | {
489 | throw new ArgumentOutOfRangeException(nameof(length));
490 | }
491 | VerifyNotDisposed();
492 |
493 | if (length == 0)
494 | {
495 | return string.Empty;
496 | }
497 |
498 | ReadOnlySpan bytes;
499 |
500 | if (length <= this.bufferSize)
501 | {
502 | EnsureBuffer(length);
503 | bytes = new ReadOnlySpan(this.buffer, this.readOffset, length);
504 |
505 | this.readOffset += length;
506 | }
507 | else
508 | {
509 | bytes = ReadBytes(length);
510 | }
511 |
512 | if (options.HasFlag(StringReadOptions.TrimNullTerminator))
513 | {
514 | // Trim the string to the first null-terminator.
515 | // IndexOf should be faster than calling TrimEnd, as the strings this is used with
516 | // will normally be short with a lot of trailing 0 bytes as padding.
517 | int terminatorIndex = bytes.IndexOf((byte)0);
518 |
519 | if (terminatorIndex >= 0)
520 | {
521 | bytes = bytes.Slice(0, terminatorIndex);
522 | }
523 | }
524 |
525 | if (options.HasFlag(StringReadOptions.TrimWhiteSpace))
526 | {
527 | bytes = bytes.Trim(AsciiWhiteSpaceChars);
528 | }
529 |
530 | return System.Text.Encoding.ASCII.GetString(bytes);
531 | }
532 |
533 | protected override void Dispose(bool disposing)
534 | {
535 | if (disposing && !this.leaveOpen)
536 | {
537 | this.stream.Dispose();
538 | }
539 |
540 | base.Dispose(disposing);
541 | }
542 |
543 | ///
544 | /// Ensures that the buffer contains at least the number of bytes requested.
545 | ///
546 | /// The minimum number of bytes the buffer should contain.
547 | /// The end of the stream has been reached.
548 | private void EnsureBuffer(int count)
549 | {
550 | if ((this.readOffset + count) > this.readLength)
551 | {
552 | FillBuffer(count);
553 | }
554 | }
555 |
556 | ///
557 | /// Fills the buffer with at least the number of bytes requested.
558 | ///
559 | /// The minimum number of bytes to place in the buffer.
560 | /// The end of the stream has been reached.
561 | private void FillBuffer(int minBytes)
562 | {
563 | if (!TryFillBuffer(minBytes))
564 | {
565 | ThrowEndOfStreamException();
566 | }
567 |
568 | static void ThrowEndOfStreamException()
569 | {
570 | throw new EndOfStreamException();
571 | }
572 | }
573 |
574 | ///
575 | /// Reads the specified number of bytes from the stream.
576 | ///
577 | /// The span.
578 | /// The number of bytes read from the stream.
579 | private int ReadInternal(Span destination)
580 | {
581 | int count = destination.Length;
582 |
583 | if (count == 0)
584 | {
585 | return 0;
586 | }
587 |
588 | if ((this.readOffset + count) <= this.readLength)
589 | {
590 | new ReadOnlySpan(this.buffer, this.readOffset, count).CopyTo(destination);
591 | this.readOffset += count;
592 |
593 | return count;
594 | }
595 | else
596 | {
597 | int totalBytesRead;
598 |
599 | if (count < this.bufferSize)
600 | {
601 | // This is an optimization for sequentially reading small ranges of bytes
602 | // from a file.
603 | // For example, a file signature that will be followed by other header data.
604 | //
605 | // TryFillBuffer may return fewer bytes than were requested if the end of the
606 | // stream has been reached.
607 | totalBytesRead = TryFillBuffer(count) ? count : this.readLength;
608 |
609 | if (totalBytesRead > 0)
610 | {
611 | new ReadOnlySpan(this.buffer, this.readOffset, totalBytesRead).CopyTo(destination.Slice(0, totalBytesRead));
612 |
613 | this.readOffset += totalBytesRead;
614 | }
615 | }
616 | else
617 | {
618 | // Ensure that any bytes at the end of the current buffer are included.
619 | int bytesUnread = this.readLength - this.readOffset;
620 |
621 | if (bytesUnread > 0)
622 | {
623 | new ReadOnlySpan(this.buffer, this.readOffset, bytesUnread).CopyTo(destination);
624 | }
625 |
626 | totalBytesRead = bytesUnread;
627 | int bytesRemaining = count - bytesUnread;
628 |
629 | // Invalidate the existing buffer.
630 | this.readOffset = 0;
631 | this.readLength = 0;
632 |
633 | totalBytesRead += this.stream.Read(destination.Slice(bytesUnread, bytesRemaining));
634 | }
635 |
636 | return totalBytesRead;
637 | }
638 | }
639 |
640 | ///
641 | /// Attempts to fill the buffer with at least the number of bytes requested.
642 | ///
643 | /// The minimum number of bytes to place in the buffer.
644 | ///
645 | /// if the buffer contains at least ; otherwise, .
646 | ///
647 | private bool TryFillBuffer(int minBytes)
648 | {
649 | int bytesUnread = this.readLength - this.readOffset;
650 |
651 | if (bytesUnread > 0)
652 | {
653 | Buffer.BlockCopy(this.buffer, this.readOffset, this.buffer, 0, bytesUnread);
654 | }
655 |
656 | this.readOffset = 0;
657 | this.readLength = bytesUnread;
658 | do
659 | {
660 | int bytesRead = this.stream.Read(this.buffer, this.readLength, this.bufferSize - this.readLength);
661 |
662 | if (bytesRead == 0)
663 | {
664 | return false;
665 | }
666 |
667 | this.readLength += bytesRead;
668 |
669 | } while (this.readLength < minBytes);
670 |
671 | return true;
672 | }
673 |
674 | ///
675 | /// Verifies that the has not been disposed.
676 | ///
677 | /// The object has been disposed.
678 | private void VerifyNotDisposed()
679 | {
680 | if (this.IsDisposed)
681 | {
682 | throw new ObjectDisposedException(nameof(EndianBinaryReader));
683 | }
684 | }
685 | }
686 | }
687 |
--------------------------------------------------------------------------------
/src/IO/Endianess.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | namespace PaintShopProFiletype.IO
13 | {
14 | internal enum Endianess
15 | {
16 | Big = 0,
17 | Little
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/IO/StringReadOptions.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | namespace PaintShopProFiletype.IO
13 | {
14 | internal enum StringReadOptions
15 | {
16 | None = 0,
17 | TrimWhiteSpace = (1 << 0),
18 | TrimNullTerminator = (1 << 1),
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/IgnoredWords.dic:
--------------------------------------------------------------------------------
1 | attr
2 | config
3 | deserialize
4 | dest
5 | endianess
6 | exif
7 | filetype
8 | gps
9 | idx
10 | ifd
11 | interop
12 | jpg
13 | metadata
14 | nq
15 | pdn
16 | plugin
17 | pspformat
18 | ptr
19 | rect
20 | rgb
21 | RGBQUAD
22 | src
23 |
--------------------------------------------------------------------------------
/src/PSPFileType.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using System;
13 | using System.Collections.Generic;
14 | using System.IO;
15 | using PaintDotNet;
16 | using PaintDotNet.IndirectUI;
17 | using PaintDotNet.PropertySystem;
18 | using PaintShopProFiletype.Properties;
19 |
20 | namespace PaintShopProFiletype
21 | {
22 | [PluginSupportInfo(typeof(PluginSupportInfo))]
23 | public sealed class PaintShopProFormat : PropertyBasedFileType
24 | {
25 | private static readonly string[] FileExtensions = new string[] { ".psp", ".pspimage", ".pspbrush", ".jfr", ".pspframe", ".pspmask", ".tub", ".psptube" };
26 |
27 | private readonly IServiceProvider services;
28 |
29 | public PaintShopProFormat(IServiceProvider services) : base(
30 | "Paint Shop Pro",
31 | new FileTypeOptions()
32 | {
33 | SupportsLayers = true,
34 | LoadExtensions = FileExtensions,
35 | SaveExtensions = FileExtensions,
36 | })
37 | {
38 | this.services = services;
39 | }
40 |
41 | protected override Document OnLoad(Stream input)
42 | {
43 | PSPFile file = new PSPFile(this.services);
44 | return file.Load(input);
45 | }
46 |
47 | public override PropertyCollection OnCreateSavePropertyCollection()
48 | {
49 | List properties = new List()
50 | {
51 | StaticListChoiceProperty.CreateForEnum(PropertyNames.FileVersion, FileVersion.Version6, false),
52 | StaticListChoiceProperty.CreateForEnum(PropertyNames.CompressionType, CompressionFormats.LZ77, false)
53 | };
54 |
55 | return new PropertyCollection(properties);
56 | }
57 |
58 | public override ControlInfo OnCreateSaveConfigUI(PropertyCollection props)
59 | {
60 | ControlInfo info = PropertyBasedFileType.CreateDefaultSaveConfigUI(props);
61 |
62 | PropertyControlInfo fileVersionPCI = info.FindControlForPropertyName(PropertyNames.FileVersion)!;
63 |
64 | fileVersionPCI.ControlProperties[ControlInfoPropertyNames.DisplayName]!.Value = Resources.FileVersionText;
65 | fileVersionPCI.SetValueDisplayName(FileVersion.Version5, Resources.Version5);
66 | fileVersionPCI.SetValueDisplayName(FileVersion.Version6, Resources.Version6AndLater);
67 |
68 | PropertyControlInfo compressonTypePCI = info.FindControlForPropertyName(PropertyNames.CompressionType)!;
69 |
70 | compressonTypePCI.ControlProperties[ControlInfoPropertyNames.DisplayName]!.Value = Resources.CompressionFormatText;
71 | compressonTypePCI.ControlType.Value = PropertyControlType.RadioButton;
72 | compressonTypePCI.SetValueDisplayName(CompressionFormats.None, Resources.CompressionFormatNoneText);
73 | compressonTypePCI.SetValueDisplayName(CompressionFormats.LZ77, Resources.CompressionFormatLZ77Text);
74 |
75 | return info;
76 | }
77 |
78 | private enum PropertyNames
79 | {
80 | FileVersion,
81 | CompressionType
82 | }
83 |
84 | protected override void OnSaveT(Document input, Stream output, PropertyBasedSaveConfigToken token, Surface scratchSurface, ProgressEventHandler progressCallback)
85 | {
86 | PSPFile file = new PSPFile(this.services);
87 |
88 | CompressionFormats format = (CompressionFormats)token.GetProperty(PropertyNames.CompressionType)!.Value!;
89 | FileVersion version = (FileVersion)token.GetProperty(PropertyNames.FileVersion)!.Value!;
90 |
91 | file.Save(input, output, format, scratchSurface, (ushort)version, progressCallback);
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/PSPFileTypeFactory.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using PaintDotNet;
13 |
14 | namespace PaintShopProFiletype
15 | {
16 | public sealed class PSPFileTypeFactory : IFileTypeFactory2
17 | {
18 | public FileType[] GetFileTypeInstances(IFileTypeHost host)
19 | {
20 | return new FileType[] { new PaintShopProFormat(host.Services) };
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/PSPSections/ChannelSubBlock.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using CommunityToolkit.HighPerformance;
13 | using CommunityToolkit.HighPerformance.Buffers;
14 | using System;
15 | using System.IO;
16 | using System.IO.Compression;
17 |
18 | namespace PaintShopProFiletype.PSPSections
19 | {
20 | internal sealed class ChannelSubBlock
21 | {
22 | public uint chunkSize;
23 | public uint compressedChannelLength;
24 | public uint uncompressedChannelLength;
25 | public PSPDIBType bitmapType;
26 | public PSPChannelType channelType;
27 | public byte[] channelData;
28 |
29 | private const uint Version6HeaderSize = 16U;
30 | private const uint Version5HeaderSize = 12U;
31 |
32 | public ChannelSubBlock(IO.EndianBinaryReader br, PSPCompression compression, ushort majorVersion)
33 | {
34 | this.chunkSize = majorVersion > PSPConstants.majorVersion5 ? br.ReadUInt32() : 0U;
35 | this.compressedChannelLength = br.ReadUInt32();
36 | this.uncompressedChannelLength = br.ReadUInt32();
37 | this.bitmapType = (PSPDIBType)br.ReadUInt16();
38 | this.channelType = (PSPChannelType)br.ReadUInt16();
39 | this.channelData = Array.Empty();
40 |
41 | if (majorVersion > PSPConstants.majorVersion5)
42 | {
43 | long bytesToSkip = (long)this.chunkSize - Version6HeaderSize;
44 |
45 | if (bytesToSkip > 0)
46 | {
47 | br.Position += bytesToSkip;
48 | }
49 | }
50 |
51 | if (this.compressedChannelLength > 0U)
52 | {
53 | switch (compression)
54 | {
55 | case PSPCompression.None:
56 | this.channelData = br.ReadBytes((int)this.compressedChannelLength);
57 | break;
58 | case PSPCompression.RLE:
59 | using (MemoryOwner compressedDataOwner = MemoryOwner.Allocate((int)this.compressedChannelLength))
60 | {
61 | Span compressedData = compressedDataOwner.Span;
62 |
63 | br.ReadExactly(compressedData);
64 | this.channelData = new byte[this.uncompressedChannelLength];
65 |
66 | RLE.Decompress(compressedData, this.channelData);
67 | }
68 | break;
69 | case PSPCompression.LZ77:
70 | using (MemoryOwner compressedDataOwner = MemoryOwner.Allocate((int)this.compressedChannelLength))
71 | {
72 | br.ReadExactly(compressedDataOwner.Span);
73 | this.channelData = new byte[this.uncompressedChannelLength];
74 |
75 | using (Stream compressedStream = compressedDataOwner.AsStream())
76 | using (ZLibStream decompressionStream = new ZLibStream(compressedStream, CompressionMode.Decompress))
77 | {
78 | Span channelDataSpan = this.channelData;
79 | int bytesRead = 0;
80 |
81 | while ((bytesRead = decompressionStream.Read(channelDataSpan)) > 0)
82 | {
83 | channelDataSpan = channelDataSpan.Slice(bytesRead);
84 | }
85 | }
86 | }
87 | break;
88 | default: throw new FormatException($"Unsupported channel compression type: {compression}.");
89 | }
90 | }
91 | }
92 |
93 | public ChannelSubBlock(ushort majorVersion, uint uncompressedSize)
94 | {
95 | this.chunkSize = majorVersion > PSPConstants.majorVersion5 ? Version6HeaderSize : Version5HeaderSize;
96 | this.compressedChannelLength = 0;
97 | this.uncompressedChannelLength = uncompressedSize;
98 | this.bitmapType = PSPDIBType.Image;
99 | this.channelType = PSPChannelType.Composite;
100 | this.channelData = Array.Empty();
101 | }
102 |
103 | public void Save(BinaryWriter bw, ushort majorVersion)
104 | {
105 | bw.Write(PSPConstants.blockIdentifier);
106 | bw.Write((ushort)PSPBlockID.Channel);
107 | if (majorVersion > PSPConstants.majorVersion5)
108 | {
109 | bw.Write(Version6HeaderSize + this.compressedChannelLength);
110 | bw.Write(this.chunkSize);
111 | }
112 | else
113 | {
114 | bw.Write(Version5HeaderSize); // initial size
115 | bw.Write(Version5HeaderSize + this.compressedChannelLength);
116 | }
117 | bw.Write(this.compressedChannelLength);
118 | bw.Write(this.uncompressedChannelLength);
119 | bw.Write((ushort)this.bitmapType);
120 | bw.Write((ushort)this.channelType);
121 |
122 | if (this.channelData != null)
123 | {
124 | bw.Write(this.channelData);
125 | }
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/PSPSections/ColorPaletteBlock.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | namespace PaintShopProFiletype.PSPSections
13 | {
14 | internal sealed class ColorPaletteBlock
15 | {
16 | public uint chunkSize;
17 | public uint entriesCount;
18 |
19 | public RGBQUAD[] entries;
20 |
21 | private const uint HeaderSize = 8U;
22 |
23 | public ColorPaletteBlock(IO.EndianBinaryReader br, ushort majorVersion)
24 | {
25 | this.chunkSize = majorVersion > PSPConstants.majorVersion5 ? br.ReadUInt32() : 0;
26 | this.entriesCount = br.ReadUInt32();
27 |
28 | this.entries = new RGBQUAD[this.entriesCount];
29 | for (int i = 0; i < this.entriesCount; i++)
30 | {
31 | this.entries[i] = new RGBQUAD(br);
32 | }
33 |
34 | if (majorVersion > PSPConstants.majorVersion5)
35 | {
36 | long bytesToSkip = (long)this.chunkSize - HeaderSize;
37 |
38 | if (bytesToSkip > 0)
39 | {
40 | br.Position += bytesToSkip;
41 | }
42 | }
43 | }
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/PSPSections/CompositeImageBlock.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using PaintShopProFiletype.IO;
13 | using System;
14 | using System.Diagnostics;
15 | using System.IO;
16 |
17 | namespace PaintShopProFiletype.PSPSections
18 | {
19 | internal sealed class CompositeImageAttributesChunk
20 | {
21 | public uint chunkSize;
22 | public int width;
23 | public int height;
24 | public ushort bitDepth;
25 | public PSPCompression compressionType; // ushort
26 | public ushort planeCount;
27 | public uint colorCount;
28 | public PSPCompositeImageType compositeImageType; // ushort
29 |
30 | private const uint HeaderSize = 24U;
31 |
32 | #if DEBUG
33 | public CompositeImageAttributesChunk(EndianBinaryReader br)
34 | {
35 | this.chunkSize = br.ReadUInt32();
36 | this.width = br.ReadInt32();
37 | this.height = br.ReadInt32();
38 | this.bitDepth = br.ReadUInt16();
39 | this.compressionType = (PSPCompression)br.ReadUInt16();
40 | this.planeCount = br.ReadUInt16();
41 | this.colorCount = br.ReadUInt32();
42 | this.compositeImageType = (PSPCompositeImageType)br.ReadUInt16();
43 |
44 | long bytesToSkip = (long)this.chunkSize - HeaderSize;
45 | if (bytesToSkip > 0)
46 | {
47 | br.Position += bytesToSkip;
48 | }
49 | }
50 | #endif
51 | public CompositeImageAttributesChunk(int width, int height, PSPCompositeImageType imageType, PSPCompression compression)
52 | {
53 | this.chunkSize = HeaderSize;
54 | this.width = width;
55 | this.height = height;
56 | this.bitDepth = 24;
57 | this.compressionType = compression;
58 | this.planeCount = 1;
59 | this.colorCount = (1 << 24);
60 | this.compositeImageType = imageType;
61 | }
62 |
63 | public void Save(BinaryWriter bw)
64 | {
65 | bw.Write(PSPConstants.blockIdentifier);
66 | bw.Write((ushort)PSPBlockID.CompositeImageAttributes);
67 | bw.Write(HeaderSize);
68 | bw.Write(this.chunkSize);
69 | bw.Write(this.width);
70 | bw.Write(this.height);
71 | bw.Write(this.bitDepth);
72 | bw.Write((ushort)this.compressionType);
73 | bw.Write(this.planeCount);
74 | bw.Write(this.colorCount);
75 | bw.Write((ushort)this.compositeImageType);
76 | }
77 | }
78 |
79 | internal sealed class JPEGCompositeInfoChunk
80 | {
81 | public uint chunkSize;
82 | public uint compressedSize;
83 | public uint unCompressedSize;
84 | public PSPDIBType imageType;
85 |
86 | public byte[]? imageData;
87 |
88 | private const uint HeaderSize = 14U;
89 |
90 | #if DEBUG
91 | public JPEGCompositeInfoChunk(EndianBinaryReader br)
92 | {
93 | this.chunkSize = br.ReadUInt32();
94 | this.compressedSize = br.ReadUInt32();
95 | this.unCompressedSize = br.ReadUInt32();
96 | this.imageType = (PSPDIBType)br.ReadUInt16();
97 |
98 | long bytesToSkip = (long)this.chunkSize - HeaderSize;
99 | if (bytesToSkip > 0)
100 | {
101 | br.Position += bytesToSkip;
102 | }
103 |
104 | this.imageData = br.ReadBytes((int)this.compressedSize);
105 | }
106 | #endif
107 | public JPEGCompositeInfoChunk()
108 | {
109 | this.chunkSize = HeaderSize;
110 | this.unCompressedSize = 0;
111 | this.imageType = PSPDIBType.Thumbnail;
112 | this.imageData = null;
113 | }
114 |
115 | public void Save(BinaryWriter bw)
116 | {
117 | bw.Write(PSPConstants.blockIdentifier);
118 | bw.Write((ushort)PSPBlockID.JPEGImage);
119 | bw.Write(HeaderSize + this.compressedSize);
120 | bw.Write(this.chunkSize);
121 | bw.Write(this.compressedSize);
122 | bw.Write(this.unCompressedSize);
123 | bw.Write((ushort)this.imageType);
124 | bw.Write(this.imageData!);
125 | }
126 | }
127 |
128 | internal sealed class CompositeImageInfoChunk
129 | {
130 | public uint chunkSize;
131 | public ushort bitmapCount;
132 | public ushort channelCount;
133 | public ColorPaletteBlock? paletteSubBlock;
134 | public ChannelSubBlock[] channelBlocks;
135 |
136 | private const uint HeaderSize = 8U;
137 |
138 | #if DEBUG
139 | public CompositeImageInfoChunk(EndianBinaryReader br, CompositeImageAttributesChunk attr, ushort majorVersion)
140 | {
141 | this.chunkSize = br.ReadUInt32();
142 | this.bitmapCount = br.ReadUInt16();
143 | this.channelCount = br.ReadUInt16();
144 | this.paletteSubBlock = null;
145 | this.channelBlocks = new ChannelSubBlock[this.channelCount];
146 |
147 | long bytesToSkip = (long)this.chunkSize - HeaderSize;
148 | if (bytesToSkip > 0)
149 | {
150 | br.Position += bytesToSkip;
151 | }
152 |
153 | int index = 0;
154 | do
155 | {
156 | uint blockSig = br.ReadUInt32();
157 | if (blockSig != PSPConstants.blockIdentifier)
158 | {
159 | throw new FormatException(Properties.Resources.InvalidBlockSignature);
160 | }
161 | PSPBlockID blockType = (PSPBlockID)br.ReadUInt16();
162 | #pragma warning disable IDE0059 // Value assigned to symbol is never used
163 | uint chunkLength = br.ReadUInt32();
164 | #pragma warning restore IDE0059 // Value assigned to symbol is never used
165 |
166 | switch (blockType)
167 | {
168 | case PSPBlockID.ColorPalette:
169 | this.paletteSubBlock = new ColorPaletteBlock(br, majorVersion);
170 | break;
171 | case PSPBlockID.Channel:
172 | ChannelSubBlock block = new ChannelSubBlock(br, attr.compressionType, majorVersion);
173 | this.channelBlocks[index] = block;
174 | index++;
175 | break;
176 | }
177 | }
178 | while (index < this.channelCount);
179 | }
180 | #endif
181 | public CompositeImageInfoChunk()
182 | {
183 | this.chunkSize = HeaderSize;
184 | this.bitmapCount = 1;
185 | this.channelCount = 3;
186 | this.paletteSubBlock = null;
187 | this.channelBlocks = null!;
188 | }
189 |
190 | public void Save(BinaryWriter writer, ushort majorVersion)
191 | {
192 | writer.Write(PSPConstants.blockIdentifier);
193 | writer.Write((ushort)PSPBlockID.CompositeImage);
194 |
195 | using (new BlockLengthWriter(writer))
196 | {
197 | writer.Write(this.chunkSize);
198 | writer.Write(this.bitmapCount);
199 | writer.Write(this.channelCount);
200 |
201 | for (int i = 0; i < this.channelCount; i++)
202 | {
203 | this.channelBlocks[i].Save(writer, majorVersion);
204 | }
205 | }
206 | }
207 |
208 | }
209 |
210 | internal class CompositeImageBlock
211 | {
212 | private readonly uint blockSize;
213 | private readonly uint attrChunkCount;
214 | private readonly CompositeImageAttributesChunk[] attrChunks;
215 | private readonly JPEGCompositeInfoChunk? jpegChunk;
216 | private readonly CompositeImageInfoChunk? imageChunk;
217 |
218 | private const uint HeaderSize = 8U;
219 |
220 | public CompositeImageBlock(CompositeImageAttributesChunk[] attributes, JPEGCompositeInfoChunk jpg, CompositeImageInfoChunk info)
221 | {
222 | this.blockSize = HeaderSize;
223 | this.attrChunkCount = (uint)attributes.Length;
224 | this.attrChunks = attributes;
225 | this.jpegChunk = jpg;
226 | this.imageChunk = info;
227 | }
228 |
229 | #if DEBUG
230 | public CompositeImageBlock(EndianBinaryReader br, ushort majorVersion)
231 | {
232 | this.blockSize = br.ReadUInt32();
233 | this.attrChunkCount = br.ReadUInt32();
234 |
235 | long bytesToSkip = (long)this.blockSize - HeaderSize;
236 | if (bytesToSkip > 0)
237 | {
238 | br.Position += bytesToSkip;
239 | }
240 |
241 | this.attrChunks = new CompositeImageAttributesChunk[(int)this.attrChunkCount];
242 |
243 | for (int i = 0; i < this.attrChunkCount; i++)
244 | {
245 | uint blockSig = br.ReadUInt32();
246 | if (blockSig != PSPConstants.blockIdentifier)
247 | {
248 | throw new FormatException(Properties.Resources.InvalidBlockSignature);
249 | }
250 | ushort blockType = br.ReadUInt16();
251 | PSPUtil.CheckBlockType(blockType, PSPBlockID.CompositeImageAttributes);
252 | #pragma warning disable IDE0059 // Value assigned to symbol is never used
253 | uint attrChunkLength = br.ReadUInt32();
254 | #pragma warning restore IDE0059 // Value assigned to symbol is never used
255 |
256 | this.attrChunks[i] = new CompositeImageAttributesChunk(br);
257 | }
258 |
259 | for (int i = 0; i < this.attrChunkCount; i++)
260 | {
261 | uint blockSig = br.ReadUInt32();
262 | if (blockSig != PSPConstants.blockIdentifier)
263 | {
264 | throw new FormatException(Properties.Resources.InvalidBlockSignature);
265 | }
266 | PSPBlockID blockType = (PSPBlockID)br.ReadUInt16();
267 | #pragma warning disable IDE0059 // Value assigned to symbol is never used
268 | uint chunkLength = br.ReadUInt32();
269 | #pragma warning restore IDE0059 // Value assigned to symbol is never used
270 |
271 | switch (blockType)
272 | {
273 | case PSPBlockID.CompositeImage:
274 | this.imageChunk = new CompositeImageInfoChunk(br, this.attrChunks[i], majorVersion);
275 | break;
276 | case PSPBlockID.JPEGImage:
277 | this.jpegChunk = new JPEGCompositeInfoChunk(br);
278 | break;
279 | }
280 | }
281 |
282 | if (this.jpegChunk?.imageData != null)
283 | {
284 | using (MemoryStream ms = new MemoryStream(this.jpegChunk.imageData))
285 | {
286 | using (System.Drawing.Bitmap th = new System.Drawing.Bitmap(ms))
287 | {
288 | Debug.WriteLine(string.Format("JPEG thumbnail size: {0}x{1}", th.Width, th.Height));
289 | }
290 | }
291 | }
292 | }
293 | #endif
294 |
295 | ///
296 | /// Saves the CompositeImageBlock to the file.
297 | ///
298 | /// The BinaryWriter to write to.
299 | public void Save(BinaryWriter bw)
300 | {
301 | bw.Write(PSPConstants.blockIdentifier);
302 | bw.Write((ushort)PSPBlockID.CompositeImageBank);
303 |
304 | using (new BlockLengthWriter(bw))
305 | {
306 | bw.Write(this.blockSize);
307 | bw.Write(this.attrChunkCount);
308 |
309 | foreach (CompositeImageAttributesChunk item in this.attrChunks)
310 | {
311 | item.Save(bw);
312 | }
313 |
314 | this.jpegChunk!.Save(bw);
315 | this.imageChunk!.Save(bw, PSPConstants.majorVersion6);
316 | }
317 | }
318 |
319 | }
320 | }
321 |
--------------------------------------------------------------------------------
/src/PSPSections/CreatorBlock.DebugView.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using System;
13 |
14 | #nullable enable
15 |
16 | namespace PaintShopProFiletype.PSPSections
17 | {
18 | internal sealed partial class CreatorBlock
19 | {
20 | private sealed class DebugView
21 | {
22 | private readonly CreatorBlock creatorBlock;
23 |
24 | public DebugView(CreatorBlock creatorBlock) => this.creatorBlock = creatorBlock;
25 |
26 | public string Title => this.creatorBlock.title;
27 |
28 | public DateTime CreateDate => UnixEpochLocal.AddSeconds(this.creatorBlock.createDate);
29 |
30 | public DateTime ModDate => UnixEpochLocal.AddSeconds(this.creatorBlock.modDate);
31 |
32 | public string Artist => this.creatorBlock.artist;
33 |
34 | public string CopyRight => this.creatorBlock.copyRight;
35 |
36 | public string Description => this.creatorBlock.description;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/PSPSections/CreatorBlock.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using PaintShopProFiletype.IO;
13 | using System;
14 | using System.Buffers;
15 | using System.Diagnostics;
16 | using System.IO;
17 | using System.Runtime.CompilerServices;
18 | using System.Text;
19 |
20 | namespace PaintShopProFiletype.PSPSections
21 | {
22 | [Serializable]
23 | [DebuggerTypeProxy(typeof(DebugView))]
24 | internal sealed partial class CreatorBlock
25 | {
26 | private static readonly DateTime UnixEpochLocal = new DateTime(1970, 1, 1).ToLocalTime();
27 |
28 | // These field are required for compatibility with binary serialization.
29 | #pragma warning disable IDE0044 // Add readonly modifier
30 | private string title;
31 | private uint createDate;
32 | [NonSerialized]
33 | private uint modDate;
34 | private string artist;
35 | private string copyRight;
36 | private string description;
37 | #pragma warning restore IDE0044 // Add readonly modifier
38 |
39 | public CreatorBlock(EndianBinaryReader reader, uint blockLength)
40 | {
41 | this.title = string.Empty;
42 | this.createDate = 0;
43 | this.modDate = 0;
44 | this.artist = string.Empty;
45 | this.copyRight = string.Empty;
46 | this.description = string.Empty;
47 |
48 | long endOffset = reader.Position + blockLength;
49 |
50 | while (reader.Position < endOffset && reader.ReadUInt32() == PSPConstants.fieldIdentifier)
51 | {
52 | PSPCreatorFieldID field = (PSPCreatorFieldID)reader.ReadUInt16();
53 | uint fieldLength = reader.ReadUInt32();
54 |
55 | switch (field)
56 | {
57 | case PSPCreatorFieldID.Title:
58 | this.title = reader.ReadAsciiString((int)fieldLength, StringReadOptions.TrimWhiteSpace);
59 | break;
60 | case PSPCreatorFieldID.CreateDate:
61 | this.createDate = reader.ReadUInt32();
62 | break;
63 | case PSPCreatorFieldID.ModifiedDate:
64 | this.modDate = reader.ReadUInt32();
65 | break;
66 | case PSPCreatorFieldID.Artist:
67 | this.artist = reader.ReadAsciiString((int)fieldLength, StringReadOptions.TrimWhiteSpace);
68 | break;
69 | case PSPCreatorFieldID.Copyright:
70 | this.copyRight = reader.ReadAsciiString((int)fieldLength, StringReadOptions.TrimWhiteSpace);
71 | break;
72 | case PSPCreatorFieldID.Description:
73 | this.description = reader.ReadAsciiString((int)fieldLength, StringReadOptions.TrimWhiteSpace);
74 | break;
75 | #if DEBUG
76 | case PSPCreatorFieldID.ApplicationID:
77 | PSPCreatorAppID appID = (PSPCreatorAppID)reader.ReadUInt32();
78 | break;
79 | case PSPCreatorFieldID.ApplicationVersion:
80 | uint appVersion = reader.ReadUInt32();
81 | break;
82 | #endif
83 | default:
84 | reader.Position += fieldLength;
85 | break;
86 | }
87 | }
88 | }
89 |
90 | public string Title => this.title;
91 |
92 | public uint CreateDate => this.createDate;
93 |
94 | public uint ModDate => this.modDate;
95 |
96 | public string Artist => this.artist;
97 |
98 | public string CopyRight => this.copyRight;
99 |
100 | public string Description => this.description;
101 |
102 | public void Save(BinaryWriter writer, ushort majorVersion)
103 | {
104 | writer.Write(PSPConstants.blockIdentifier);
105 | writer.Write((ushort)PSPBlockID.Creator);
106 |
107 | if (majorVersion <= PSPConstants.majorVersion5)
108 | {
109 | writer.Write(0U); // Initial data chunk length, always 0.
110 | }
111 |
112 | using (new BlockLengthWriter(writer))
113 | {
114 | if (!string.IsNullOrEmpty(this.title))
115 | {
116 | WriteAsciiField(writer, PSPCreatorFieldID.Title, this.title);
117 | }
118 |
119 | if (this.createDate != 0U)
120 | {
121 | WriteUInt32Field(writer, PSPCreatorFieldID.CreateDate, this.createDate);
122 | }
123 |
124 | WriteUInt32Field(writer, PSPCreatorFieldID.ModifiedDate, GetCurrentUnixTimestamp());
125 |
126 | if (!string.IsNullOrEmpty(this.artist))
127 | {
128 | WriteAsciiField(writer, PSPCreatorFieldID.Artist, this.artist);
129 | }
130 |
131 | if (!string.IsNullOrEmpty(this.copyRight))
132 | {
133 | WriteAsciiField(writer, PSPCreatorFieldID.Copyright, this.copyRight);
134 | }
135 |
136 | if (!string.IsNullOrEmpty(this.description))
137 | {
138 | WriteAsciiField(writer, PSPCreatorFieldID.Description, this.description);
139 | }
140 | }
141 | }
142 |
143 | ///
144 | /// Gets the current date and time in Unix format (the number of seconds from 1/1/1970)
145 | ///
146 | /// The current date and time in Unix format
147 | private static uint GetCurrentUnixTimestamp()
148 | {
149 | TimeSpan t = (DateTime.Now - UnixEpochLocal);
150 |
151 | return (uint)t.TotalSeconds;
152 | }
153 |
154 | [SkipLocalsInit]
155 | private static void WriteAsciiField(BinaryWriter writer, PSPCreatorFieldID field, string value)
156 | {
157 | const int MaxStackBufferLength = 256;
158 |
159 | Span buffer = stackalloc byte[MaxStackBufferLength];
160 |
161 | byte[]? arrayFromPool = null;
162 |
163 | try
164 | {
165 | int maxNameLengthInBytes = Encoding.ASCII.GetMaxByteCount(value.Length);
166 |
167 | if (maxNameLengthInBytes > MaxStackBufferLength)
168 | {
169 | arrayFromPool = ArrayPool.Shared.Rent(maxNameLengthInBytes);
170 | buffer = arrayFromPool;
171 | }
172 |
173 | int bytesWritten = Encoding.ASCII.GetBytes(value, buffer);
174 |
175 | buffer = buffer.Slice(0, bytesWritten);
176 |
177 | writer.Write(PSPConstants.fieldIdentifier);
178 | writer.Write((ushort)field);
179 | writer.Write((uint)buffer.Length);
180 | writer.Write(buffer);
181 | }
182 | finally
183 | {
184 | if (arrayFromPool != null)
185 | {
186 | ArrayPool.Shared.Return(arrayFromPool);
187 | }
188 | }
189 | }
190 |
191 | private static void WriteUInt32Field(BinaryWriter writer, PSPCreatorFieldID field, uint value)
192 | {
193 | writer.Write(PSPConstants.fieldIdentifier);
194 | writer.Write((ushort)field);
195 | writer.Write(sizeof(uint));
196 | writer.Write(value);
197 | }
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/src/PSPSections/CreatorBlockSerialization.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using PaintDotNet;
13 | using System;
14 | using System.IO;
15 | using System.Runtime.Serialization;
16 | using System.Runtime.Serialization.Formatters.Binary;
17 | using System.Text;
18 | using System.Xml;
19 |
20 | #nullable enable
21 |
22 | namespace PaintShopProFiletype.PSPSections
23 | {
24 | internal static class CreatorBlockSerialization
25 | {
26 | private const string PSPCreatorMetaDataBinaryFormatter = "PSPFormatCreatorData";
27 | private const string PSPCreatorMetaDataDataContract = "PSPFormatCreatorData2";
28 |
29 | public static CreatorBlock? Deserialize(Document input)
30 | {
31 | CreatorBlock? result = null;
32 |
33 | string? creatorData = input.Metadata.GetUserValue(PSPCreatorMetaDataDataContract);
34 | if (!string.IsNullOrEmpty(creatorData))
35 | {
36 | result = DeserializeDataContract(creatorData);
37 | }
38 | else
39 | {
40 | creatorData = input.Metadata.GetUserValue(PSPCreatorMetaDataBinaryFormatter);
41 | if (!string.IsNullOrEmpty(creatorData))
42 | {
43 | result = DeserializeBinaryFormatter(creatorData);
44 | }
45 | }
46 |
47 | return result;
48 | }
49 |
50 | public static void Serialize(CreatorBlock? input, Document document)
51 | {
52 | if (input is null)
53 | {
54 | return;
55 | }
56 |
57 | string data = SerializeDataContract(input);
58 |
59 | document.Metadata.SetUserValue(PSPCreatorMetaDataDataContract, data);
60 | }
61 |
62 | private static CreatorBlock DeserializeBinaryFormatter(string base64)
63 | {
64 | byte[] bytes = Convert.FromBase64String(base64);
65 |
66 | using (MemoryStream stream = new MemoryStream(bytes))
67 | {
68 | #pragma warning disable SYSLIB0011
69 | BinaryFormatter formatter = new BinaryFormatter() { Binder = new SelfBinder() };
70 | return (CreatorBlock)formatter.Deserialize(stream);
71 | #pragma warning restore SYSLIB0011
72 | }
73 | }
74 |
75 | private static CreatorBlock DeserializeDataContract(string text)
76 | {
77 | CreatorBlock result;
78 |
79 | using (StringReader stringReader = new StringReader(text))
80 | using (XmlReader xmlReader = XmlReader.Create(stringReader))
81 | {
82 | result = (CreatorBlock)new DataContractSerializer(typeof(CreatorBlock)).ReadObject(xmlReader)!;
83 | }
84 |
85 | return result;
86 | }
87 |
88 | private static string SerializeDataContract(CreatorBlock creatorBlock)
89 | {
90 | StringBuilder builder = new StringBuilder();
91 |
92 | using (XmlWriter writer = XmlWriter.Create(builder))
93 | {
94 | new DataContractSerializer(typeof(CreatorBlock)).WriteObject(writer, creatorBlock);
95 | }
96 |
97 | return builder.ToString();
98 | }
99 |
100 | ///
101 | /// Binds the serialization to types in the currently loaded assembly.
102 | ///
103 | private class SelfBinder : SerializationBinder
104 | {
105 | public override Type? BindToType(string assemblyName, string typeName)
106 | {
107 | return Type.GetType(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0},{1}", typeName, assemblyName));
108 | }
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/PSPSections/CreatorBlockSerializedData.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using System.Runtime.Serialization;
13 |
14 | #nullable enable
15 |
16 | namespace PaintShopProFiletype.PSPSections
17 | {
18 | [DataContract]
19 | internal sealed class CreatorBlockSerializedData
20 | {
21 | [DataMember(Order = 0)]
22 | private string title;
23 | [DataMember(Order = 1)]
24 | private uint createDate;
25 | [DataMember(Order = 2)]
26 | private string artist;
27 | [DataMember(Order = 3)]
28 | private string copyRight;
29 | [DataMember(Order = 4)]
30 | private string description;
31 |
32 | public CreatorBlockSerializedData()
33 | {
34 | this.title = string.Empty;
35 | this.createDate = 0;
36 | this.artist = string.Empty;
37 | this.copyRight = string.Empty;
38 | this.description = string.Empty;
39 | }
40 |
41 | public CreatorBlockSerializedData(CreatorBlock creatorBlock)
42 | {
43 | this.title = creatorBlock.Title;
44 | this.createDate = creatorBlock.CreateDate;
45 | this.artist = creatorBlock.Artist;
46 | this.copyRight = creatorBlock.CopyRight;
47 | this.description = creatorBlock.Description;
48 | }
49 |
50 | public string Title => this.title;
51 |
52 | public uint CreateDate => this.createDate;
53 |
54 | public string Artist => this.artist;
55 |
56 | public string CopyRight => this.copyRight;
57 |
58 | public string Description => this.description;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/PSPSections/ExtendedDataBlock.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using PaintShopProFiletype.IO;
13 | using System;
14 | using System.Collections.Generic;
15 |
16 | namespace PaintShopProFiletype.PSPSections
17 | {
18 | internal class ExtendedDataBlock
19 | {
20 | private readonly uint blockLength;
21 | private readonly List> values;
22 |
23 | public ExtendedDataBlock(EndianBinaryReader br, uint blockLength)
24 | {
25 | this.blockLength = blockLength;
26 | this.values = new List>();
27 | Load(br);
28 | }
29 |
30 | public short TryGetTransparencyIndex()
31 | {
32 | short index = -1;
33 |
34 | foreach (var item in this.values)
35 | {
36 | if (item.Key == PSPExtendedDataID.TransparencyIndex)
37 | {
38 | index = BitConverter.ToInt16(item.Value, 0);
39 | break;
40 | }
41 | }
42 |
43 | return index;
44 | }
45 |
46 | private void Load(EndianBinaryReader br)
47 | {
48 | long startOffset = br.Position;
49 |
50 | long endOffset = startOffset + this.blockLength;
51 |
52 | while ((br.Position < endOffset) && br.ReadUInt32() == PSPConstants.fieldIdentifier)
53 | {
54 | PSPExtendedDataID fieldID = (PSPExtendedDataID)br.ReadUInt16();
55 |
56 | uint fieldLength = br.ReadUInt32();
57 |
58 | byte[] data = br.ReadBytes((int)fieldLength);
59 |
60 | this.values.Add(new KeyValuePair(fieldID, data));
61 | }
62 |
63 | long bytesToSkip = this.blockLength - (br.Position - startOffset);
64 |
65 | if (bytesToSkip > 0)
66 | {
67 | br.Position += bytesToSkip;
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/PSPSections/FileHeader.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using System.IO;
13 |
14 | namespace PaintShopProFiletype.PSPSections
15 | {
16 | internal class FileHeader
17 | {
18 | #pragma warning disable IDE0032 // Use auto property
19 | private readonly ushort majorVersion;
20 | private readonly ushort minorVersion;
21 | #pragma warning restore IDE0032 // Use auto property
22 |
23 | public ushort Major
24 | {
25 | get
26 | {
27 | return this.majorVersion;
28 | }
29 | }
30 |
31 | public ushort Minor
32 | {
33 | get
34 | {
35 | return this.minorVersion;
36 | }
37 | }
38 |
39 | public FileHeader(ushort major)
40 | {
41 | this.majorVersion = major;
42 | this.minorVersion = 0;
43 | }
44 |
45 | public FileHeader(IO.EndianBinaryReader br)
46 | {
47 | this.majorVersion = br.ReadUInt16();
48 | this.minorVersion = br.ReadUInt16();
49 | }
50 |
51 | public void Save(BinaryWriter bw)
52 | {
53 | bw.Write(this.majorVersion);
54 | bw.Write(this.minorVersion);
55 | }
56 |
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/PSPSections/GeneralImageAttributes.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using PaintShopProFiletype.IO;
13 | using System.IO;
14 |
15 | namespace PaintShopProFiletype.PSPSections
16 | {
17 | internal class GeneralImageAttributes
18 | {
19 | #pragma warning disable IDE0032 // Use auto property
20 | private uint chunkSize;
21 | private int width;
22 | private int height;
23 | private double resValue;
24 | private ResolutionMetric resUnits; // byte
25 | private PSPCompression compressionType; // ushort
26 | private ushort bitDepth;
27 | private ushort planeCount;
28 | private uint colorCount;
29 | private byte grayScale;
30 | private uint totalImageSize;
31 | private int activeLayer;
32 | private ushort layerCount;
33 | private PSPGraphicContents graphicContents;
34 | #pragma warning restore IDE0032 // Use auto property
35 |
36 | public int Width
37 | {
38 | get
39 | {
40 | return this.width;
41 | }
42 | }
43 | public int Height
44 | {
45 | get
46 | {
47 | return this.height;
48 | }
49 | }
50 | public double ResValue
51 | {
52 | get
53 | {
54 | return this.resValue;
55 | }
56 | set
57 | {
58 | this.resValue = value;
59 | }
60 | }
61 |
62 | public ResolutionMetric ResUnit
63 | {
64 | get
65 | {
66 | return this.resUnits;
67 | }
68 | set
69 | {
70 | this.resUnits = value;
71 | }
72 | }
73 |
74 | public PSPCompression CompressionType
75 | {
76 | get
77 | {
78 | return this.compressionType;
79 | }
80 | }
81 |
82 | public ushort BitDepth
83 | {
84 | get
85 | {
86 | return this.bitDepth;
87 | }
88 | }
89 |
90 | public int LayerCount
91 | {
92 | get
93 | {
94 | return this.layerCount;
95 | }
96 | }
97 |
98 | private const uint Version6HeaderSize = 46U;
99 | private const uint Version5HeaderSize = 38U;
100 |
101 | private readonly ushort fileMajorVersion;
102 |
103 | public GeneralImageAttributes(int width, int height, PSPCompression compType, int activeLayer, int layerCount, ushort majorVersion)
104 | {
105 | this.chunkSize = majorVersion > PSPConstants.majorVersion5 ? Version6HeaderSize : Version5HeaderSize;
106 | this.width = width;
107 | this.height = height;
108 | this.compressionType = compType;
109 | this.activeLayer = activeLayer;
110 | this.layerCount = (ushort)layerCount;
111 | this.bitDepth = 24;
112 | this.colorCount = (1U << this.bitDepth);
113 | this.graphicContents |= PSPGraphicContents.RasterLayers;
114 |
115 | this.fileMajorVersion = majorVersion;
116 | }
117 |
118 | public GeneralImageAttributes(EndianBinaryReader br, ushort majorVersion)
119 | {
120 | this.fileMajorVersion = majorVersion;
121 |
122 | Load(br);
123 | }
124 |
125 | private void Load(EndianBinaryReader br)
126 | {
127 | this.chunkSize = this.fileMajorVersion > PSPConstants.majorVersion5 ? br.ReadUInt32() : 0;
128 | this.width = br.ReadInt32();
129 | this.height = br.ReadInt32();
130 | this.resValue = br.ReadDouble();
131 | this.resUnits = (ResolutionMetric)br.ReadByte();
132 | this.compressionType = (PSPCompression)br.ReadUInt16();
133 | this.bitDepth = br.ReadUInt16();
134 | this.planeCount = br.ReadUInt16();
135 | this.colorCount = br.ReadUInt32();
136 | this.grayScale = br.ReadByte();
137 | this.totalImageSize = br.ReadUInt32();
138 | this.activeLayer = br.ReadInt32();
139 | this.layerCount = br.ReadUInt16();
140 |
141 | if (this.fileMajorVersion > PSPConstants.majorVersion5)
142 | {
143 | this.graphicContents = (PSPGraphicContents)br.ReadUInt32();
144 | }
145 | else
146 | {
147 | this.graphicContents = PSPGraphicContents.None;
148 | }
149 |
150 | if (this.fileMajorVersion > PSPConstants.majorVersion5)
151 | {
152 | long bytesToSkip = (long)this.chunkSize - Version6HeaderSize;
153 |
154 | if (bytesToSkip > 0)
155 | {
156 | br.Position += bytesToSkip;
157 | }
158 | }
159 | }
160 |
161 | public void Save(BinaryWriter bw)
162 | {
163 | bw.Write(PSPConstants.blockIdentifier);
164 | bw.Write((ushort)PSPBlockID.ImageAttributes);
165 | if (this.fileMajorVersion > PSPConstants.majorVersion5)
166 | {
167 | bw.Write(Version6HeaderSize); // total size
168 | bw.Write(this.chunkSize); // total size
169 | }
170 | else
171 | {
172 | bw.Write(Version5HeaderSize); // initial size
173 | bw.Write(Version5HeaderSize); // total size
174 | }
175 | bw.Write(this.width);
176 | bw.Write(this.height);
177 | bw.Write(this.resValue);
178 | bw.Write((byte)this.resUnits);
179 | bw.Write((ushort)this.compressionType);
180 | bw.Write(this.bitDepth);
181 | bw.Write(this.planeCount);
182 | bw.Write(this.colorCount);
183 | bw.Write(this.grayScale);
184 | bw.Write(this.totalImageSize);
185 | bw.Write(this.activeLayer);
186 | bw.Write(this.layerCount);
187 |
188 | if (this.fileMajorVersion > PSPConstants.majorVersion5)
189 | {
190 | bw.Write((uint)this.graphicContents);
191 | }
192 | }
193 |
194 | public void SetGraphicContentFlag(PSPGraphicContents flag)
195 | {
196 | this.graphicContents |= flag;
197 | }
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/src/PSPSections/LayerBlock.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using PaintShopProFiletype.IO;
13 | using System;
14 | using System.Buffers;
15 | using System.Collections.Generic;
16 | using System.Drawing;
17 | using System.IO;
18 | using System.Runtime.CompilerServices;
19 | using System.Text;
20 |
21 | namespace PaintShopProFiletype.PSPSections
22 | {
23 | internal readonly struct BlendRange
24 | {
25 | public readonly uint sourceRange;
26 | public readonly uint destRange;
27 |
28 | public BlendRange(EndianBinaryReader reader)
29 | {
30 | this.sourceRange = reader.ReadUInt32();
31 | this.destRange = reader.ReadUInt32();
32 | }
33 |
34 | public BlendRange(uint sourceRange, uint destRange)
35 | {
36 | this.sourceRange = sourceRange;
37 | this.destRange = destRange;
38 | }
39 |
40 | public void Write(BinaryWriter writer)
41 | {
42 | writer.Write(this.sourceRange);
43 | writer.Write(this.destRange);
44 | }
45 | }
46 |
47 | internal sealed class LayerBitmapInfoChunk
48 | {
49 | public uint chunkSize;
50 | public ushort bitmapCount;
51 | public ushort channelCount;
52 | public ChannelSubBlock[] channels;
53 |
54 | internal const uint HeaderSize = 8U;
55 |
56 | public LayerBitmapInfoChunk(EndianBinaryReader br, PSPCompression compression, ushort majorVersion)
57 | {
58 | long startOffset = br.Position;
59 |
60 | this.chunkSize = br.ReadUInt32();
61 | this.bitmapCount = br.ReadUInt16();
62 | this.channelCount = br.ReadUInt16();
63 | this.channels = new ChannelSubBlock[this.channelCount];
64 |
65 | long bytesToSkip = this.chunkSize - (br.Position - startOffset);
66 | if (bytesToSkip > 0L)
67 | {
68 | br.Position += bytesToSkip;
69 | }
70 |
71 | for (int i = 0; i < this.channelCount; i++)
72 | {
73 | uint head = br.ReadUInt32();
74 | if (head != PSPConstants.blockIdentifier)
75 | {
76 | throw new FormatException(Properties.Resources.InvalidBlockSignature);
77 | }
78 | ushort blockID = br.ReadUInt16();
79 | PSPUtil.CheckBlockType(blockID, PSPBlockID.Channel);
80 | #pragma warning disable IDE0059 // Value assigned to symbol is never used
81 | uint size = br.ReadUInt32();
82 | #pragma warning restore IDE0059 // Value assigned to symbol is never used
83 |
84 | this.channels[i] = new ChannelSubBlock(br, compression, majorVersion);
85 | }
86 | }
87 |
88 | public LayerBitmapInfoChunk(EndianBinaryReader br, PSPCompression compression, ushort v5BitmapCount, ushort v5ChannelCount)
89 | {
90 | this.chunkSize = 0;
91 | this.bitmapCount = v5BitmapCount;
92 | this.channelCount = v5ChannelCount;
93 | this.channels = new ChannelSubBlock[this.channelCount];
94 |
95 | for (int i = 0; i < this.channelCount; i++)
96 | {
97 | uint head = br.ReadUInt32();
98 | if (head != PSPConstants.blockIdentifier)
99 | {
100 | throw new FormatException(Properties.Resources.InvalidBlockSignature);
101 | }
102 | ushort blockID = br.ReadUInt16();
103 | PSPUtil.CheckBlockType(blockID, PSPBlockID.Channel);
104 | #pragma warning disable IDE0059 // Value assigned to symbol is never used
105 | uint initialSize = br.ReadUInt32();
106 | uint size = br.ReadUInt32();
107 | #pragma warning restore IDE0059 // Value assigned to symbol is never used
108 |
109 | this.channels[i] = new ChannelSubBlock(br, compression, PSPConstants.majorVersion5);
110 | }
111 | }
112 |
113 | public LayerBitmapInfoChunk(int bitmapCount, ChannelSubBlock[] channels)
114 | {
115 | this.chunkSize = HeaderSize;
116 | this.bitmapCount = (ushort)bitmapCount;
117 | this.channelCount = checked((ushort)channels.Length);
118 | this.channels = channels;
119 | }
120 |
121 | public void Save(BinaryWriter bw, ushort majorVersion)
122 | {
123 | if (majorVersion > PSPConstants.majorVersion5)
124 | {
125 | bw.Write(this.chunkSize);
126 | bw.Write(this.bitmapCount);
127 | bw.Write(this.channelCount);
128 | }
129 |
130 | for (int i = 0; i < this.channelCount; i++)
131 | {
132 | this.channels![i].Save(bw, majorVersion);
133 | }
134 | }
135 | }
136 |
137 | internal sealed class LayerInfoChunk
138 | {
139 | internal const uint Version5ChunkLength = 375U;
140 | ///
141 | /// The base chunk size of the version 6 header, 119 bytes + 2 for the name length.
142 | ///
143 | private const uint Version6BaseChunkSize = 119 + sizeof(ushort);
144 | private const int BlendRangeCount = 5;
145 |
146 | public uint chunkSize;
147 | public string name;
148 | public PSPLayerType type;
149 | public Rectangle imageRect; // Int32
150 | public Rectangle saveRect; // Int32
151 | public byte opacity;
152 | public PSPBlendModes blendMode; // byte
153 | public PSPLayerProperties layerFlags;
154 | public byte protectedTransparency;
155 | public byte linkGroup;
156 | public Rectangle maskRect;
157 | public Rectangle saveMaskRect;
158 | public byte maskLinked;
159 | public byte maskDisabled;
160 | public byte invertMaskOnBlend;
161 | public ushort blendRangeCount;
162 | public BlendRange[] blendRanges;
163 |
164 | public ushort v5BitmapCount;
165 | public ushort v5ChannelCount;
166 |
167 | public byte useHighlightColor;
168 | public uint highlightColor;
169 |
170 | public LayerInfoChunk(EndianBinaryReader br, ushort majorVersion)
171 | {
172 | long startOffset = br.Position;
173 |
174 | if (majorVersion > PSPConstants.majorVersion5)
175 | {
176 | this.chunkSize = br.ReadUInt32();
177 | ushort nameLen = br.ReadUInt16();
178 | this.name = br.ReadAsciiString(nameLen, StringReadOptions.None);
179 | }
180 | else
181 | {
182 | this.chunkSize = 0;
183 | this.name = br.ReadAsciiString(256, StringReadOptions.TrimNullTerminator);
184 | }
185 |
186 | this.type = (PSPLayerType)br.ReadByte();
187 |
188 | if (majorVersion <= PSPConstants.majorVersion5 && this.type == PSPLayerType.Undefined)
189 | {
190 | this.type = PSPLayerType.Raster;
191 | }
192 |
193 | this.imageRect = Rectangle.FromLTRB(br.ReadInt32(), br.ReadInt32(), br.ReadInt32(), br.ReadInt32());
194 | this.saveRect = Rectangle.FromLTRB(br.ReadInt32(), br.ReadInt32(), br.ReadInt32(), br.ReadInt32());
195 | this.opacity = br.ReadByte();
196 | this.blendMode = (PSPBlendModes)br.ReadByte();
197 | this.layerFlags = (PSPLayerProperties)br.ReadByte();
198 | this.protectedTransparency = br.ReadByte();
199 | this.linkGroup = br.ReadByte();
200 | this.maskRect = Rectangle.FromLTRB(br.ReadInt32(), br.ReadInt32(), br.ReadInt32(), br.ReadInt32());
201 | this.saveMaskRect = Rectangle.FromLTRB(br.ReadInt32(), br.ReadInt32(), br.ReadInt32(), br.ReadInt32());
202 | this.maskLinked = br.ReadByte();
203 | this.maskDisabled = br.ReadByte();
204 | this.invertMaskOnBlend = br.ReadByte();
205 | this.blendRangeCount = br.ReadUInt16();
206 | this.blendRanges = new BlendRange[BlendRangeCount];
207 | for (int i = 0; i < this.blendRanges.Length; i++)
208 | {
209 | this.blendRanges[i] = new BlendRange(br);
210 | }
211 | this.v5BitmapCount = 0;
212 | this.v5ChannelCount = 0;
213 | this.useHighlightColor = 0;
214 | this.highlightColor = 0;
215 | if (majorVersion >= PSPConstants.majorVersion8)
216 | {
217 | this.useHighlightColor = br.ReadByte();
218 | this.highlightColor = br.ReadUInt32();
219 | }
220 | else if (majorVersion <= PSPConstants.majorVersion5)
221 | {
222 | this.v5BitmapCount = br.ReadUInt16();
223 | this.v5ChannelCount = br.ReadUInt16();
224 | }
225 |
226 | if (majorVersion > PSPConstants.majorVersion5)
227 | {
228 | long bytesToSkip = this.chunkSize - (br.Position - startOffset);
229 | if (bytesToSkip > 0)
230 | {
231 | br.Position += bytesToSkip;
232 | }
233 | }
234 | }
235 |
236 | public LayerInfoChunk(PaintDotNet.Layer layer, PSPBlendModes blendMode, Rectangle savedBounds, ushort majorVersion)
237 | {
238 | if (majorVersion > PSPConstants.majorVersion5)
239 | {
240 | this.chunkSize = Version6BaseChunkSize + (uint)Encoding.ASCII.GetByteCount(layer.Name);
241 | }
242 | else
243 | {
244 | this.chunkSize = 0U;
245 | }
246 | this.name = layer.Name;
247 | this.type = majorVersion > PSPConstants.majorVersion5 ? PSPLayerType.Raster : 0;
248 | this.imageRect = layer.Bounds;
249 | this.saveRect = savedBounds;
250 | this.opacity = layer.Opacity;
251 | this.blendMode = blendMode;
252 | this.layerFlags = PSPLayerProperties.None;
253 | if (layer.Visible)
254 | {
255 | this.layerFlags |= PSPLayerProperties.Visible;
256 | }
257 | this.protectedTransparency = 0;
258 | this.linkGroup = 0;
259 | this.maskRect = Rectangle.Empty;
260 | this.saveMaskRect = Rectangle.Empty;
261 | this.maskLinked = 0;
262 | this.invertMaskOnBlend = 0;
263 | this.blendRangeCount = 0;
264 | this.blendRanges = new BlendRange[BlendRangeCount];
265 | this.v5BitmapCount = 0;
266 | this.v5ChannelCount = 0;
267 | this.useHighlightColor = 0;
268 | this.highlightColor = 0;
269 | }
270 |
271 | public void Save(BinaryWriterEx bw, ushort majorVersion)
272 | {
273 | if (majorVersion > PSPConstants.majorVersion5)
274 | {
275 | bw.Write(this.chunkSize);
276 | }
277 | WriteLayerName(bw, majorVersion);
278 | bw.Write((byte)this.type);
279 | bw.Write(this.imageRect);
280 | bw.Write(this.saveRect);
281 | bw.Write(this.opacity);
282 | bw.Write((byte)this.blendMode);
283 | bw.Write((byte)this.layerFlags);
284 | bw.Write(this.protectedTransparency);
285 | bw.Write(this.linkGroup);
286 | bw.Write(this.maskRect);
287 | bw.Write(this.saveMaskRect);
288 | bw.Write(this.maskLinked);
289 | bw.Write(this.maskDisabled);
290 | bw.Write(this.invertMaskOnBlend);
291 | bw.Write(this.blendRangeCount);
292 | for (int i = 0; i < this.blendRanges.Length; i++)
293 | {
294 | this.blendRanges[i].Write(bw);
295 | }
296 | if (majorVersion <= PSPConstants.majorVersion5)
297 | {
298 | bw.Write(this.v5BitmapCount);
299 | bw.Write(this.v5ChannelCount);
300 | }
301 | }
302 |
303 | [SkipLocalsInit]
304 | private void WriteLayerName(BinaryWriter writer, ushort majorVersion)
305 | {
306 | const int MaxStackBufferLength = 256;
307 |
308 | Span buffer = stackalloc byte[MaxStackBufferLength];
309 |
310 | byte[]? arrayFromPool = null;
311 |
312 | try
313 | {
314 | int maxNameLengthInBytes = Encoding.ASCII.GetMaxByteCount(this.name.Length);
315 |
316 | if (maxNameLengthInBytes > MaxStackBufferLength)
317 | {
318 | arrayFromPool = ArrayPool.Shared.Rent(maxNameLengthInBytes);
319 | buffer = arrayFromPool;
320 | }
321 |
322 | int bytesWritten = Encoding.ASCII.GetBytes(this.name, buffer);
323 |
324 | if (majorVersion > PSPConstants.majorVersion5)
325 | {
326 | buffer = buffer.Slice(0, bytesWritten);
327 |
328 | writer.Write(checked((ushort)buffer.Length));
329 | writer.Write(buffer);
330 | }
331 | else
332 | {
333 | // Paint Shop Pro 5 and earlier use a null-terminated layer name string in a fixed 256 byte
334 | // buffer, so strings that are longer than 255 bytes will be truncated to ensure that the
335 | // null-terminator can be written.
336 | buffer = buffer.Slice(0, 256);
337 |
338 | int terminatorIndex = Math.Min(bytesWritten, 255);
339 |
340 | // Fill the remaining space in the buffer with zeros.
341 | buffer.Slice(terminatorIndex).Clear();
342 |
343 | writer.Write(buffer);
344 | }
345 | }
346 | finally
347 | {
348 | if (arrayFromPool != null)
349 | {
350 | ArrayPool.Shared.Return(arrayFromPool);
351 | }
352 | }
353 | }
354 | }
355 |
356 | internal sealed class LayerBlock
357 | {
358 | #pragma warning disable IDE0032 // Use auto property
359 | private readonly LayerInfoChunk[] layerInfoChunks;
360 | private readonly LayerBitmapInfoChunk[] layerBitmapInfo;
361 | #pragma warning restore IDE0032 // Use auto property
362 |
363 | public LayerInfoChunk[] LayerInfo
364 | {
365 | get
366 | {
367 | return this.layerInfoChunks;
368 | }
369 | }
370 |
371 | public LayerBitmapInfoChunk[] LayerBitmapInfo
372 | {
373 | get
374 | {
375 | return this.layerBitmapInfo;
376 | }
377 | }
378 |
379 | public LayerBlock(LayerInfoChunk[] infoChunks, LayerBitmapInfoChunk[] biChunks)
380 | {
381 | this.layerInfoChunks = infoChunks;
382 | this.layerBitmapInfo = biChunks;
383 | }
384 |
385 | public LayerBlock(EndianBinaryReader br, GeneralImageAttributes? imageAttributes, ushort majorVersion)
386 | {
387 | if (imageAttributes is null)
388 | {
389 | // The image attributes block must come before the layer block.
390 | throw new FormatException(Properties.Resources.InvalidPSPFile);
391 | }
392 |
393 | IList raster = CountRasterChunks(br, imageAttributes.LayerCount, majorVersion);
394 |
395 | int layerCount = raster.Count;
396 |
397 | if (layerCount == 0)
398 | {
399 | throw new FormatException(Properties.Resources.RasterLayerNotFound);
400 | }
401 |
402 | this.layerInfoChunks = new LayerInfoChunk[layerCount];
403 | this.layerBitmapInfo = new LayerBitmapInfoChunk[layerCount];
404 | PSPCompression compression = imageAttributes.CompressionType;
405 |
406 | for (int i = 0; i < layerCount; i++)
407 | {
408 | RasterLayerChunk chunk = raster[i];
409 | this.layerInfoChunks[i] = chunk.layerInfo;
410 |
411 | if (!chunk.layerInfo.saveRect.IsEmpty)
412 | {
413 | br.Position = chunk.bitmapInfoOffset;
414 | if (majorVersion <= PSPConstants.majorVersion5)
415 | {
416 | this.layerBitmapInfo[i] = new LayerBitmapInfoChunk(br, compression, chunk.layerInfo.v5BitmapCount, chunk.layerInfo.v5ChannelCount);
417 | }
418 | else
419 | {
420 | this.layerBitmapInfo[i] = new LayerBitmapInfoChunk(br, compression, majorVersion);
421 | }
422 | }
423 | }
424 | }
425 |
426 | public void Save(BinaryWriterEx bw, ushort majorVersion)
427 | {
428 | bw.Write(PSPConstants.blockIdentifier);
429 | bw.Write((ushort)PSPBlockID.LayerStart);
430 | if (majorVersion <= PSPConstants.majorVersion5)
431 | {
432 | bw.Write(0U); // Initial data chunk length, always 0.
433 | }
434 |
435 | using (new BlockLengthWriter(bw))
436 | {
437 | int count = this.layerBitmapInfo.Length;
438 |
439 | for (int i = 0; i < count; i++)
440 | {
441 | bw.Write(PSPConstants.blockIdentifier);
442 | bw.Write((ushort)PSPBlockID.Layer);
443 | if (majorVersion <= PSPConstants.majorVersion5)
444 | {
445 | bw.Write(LayerInfoChunk.Version5ChunkLength); // Initial data chunk length.
446 | }
447 |
448 | using (new BlockLengthWriter(bw))
449 | {
450 | this.layerInfoChunks[i].Save(bw, majorVersion);
451 | this.layerBitmapInfo[i].Save(bw, majorVersion);
452 | }
453 | }
454 | }
455 | }
456 |
457 | private sealed class RasterLayerChunk
458 | {
459 | public readonly LayerInfoChunk layerInfo;
460 | public readonly long bitmapInfoOffset;
461 |
462 | public RasterLayerChunk(LayerInfoChunk info, long offset)
463 | {
464 | this.layerInfo = info;
465 | this.bitmapInfoOffset = offset;
466 | }
467 | }
468 |
469 | private static IList CountRasterChunks(EndianBinaryReader reader, int layerCount, ushort majorVersion)
470 | {
471 | List rasterChunks = new List(layerCount);
472 |
473 | int index = 0;
474 | while (index < layerCount)
475 | {
476 | uint head = reader.ReadUInt32();
477 | if (head != PSPConstants.blockIdentifier)
478 | {
479 | throw new FormatException(Properties.Resources.InvalidBlockSignature);
480 | }
481 | PSPBlockID blockID = (PSPBlockID)reader.ReadUInt16();
482 | #pragma warning disable IDE0059 // Value assigned to symbol is never used
483 | uint initialBlockLength = majorVersion <= PSPConstants.majorVersion5 ? reader.ReadUInt32() : 0;
484 | #pragma warning restore IDE0059 // Value assigned to symbol is never used
485 | uint blockLength = reader.ReadUInt32();
486 |
487 | if (blockID == PSPBlockID.Layer)
488 | {
489 | index++;
490 | long endOffset = reader.Position + blockLength;
491 |
492 | LayerInfoChunk chunk = new LayerInfoChunk(reader, majorVersion);
493 | long currentOffset = reader.Position;
494 |
495 | switch (chunk.type)
496 | {
497 | case PSPLayerType.Raster:
498 | case PSPLayerType.FloatingRasterSelection:
499 | if (majorVersion >= PSPConstants.majorVersion12)
500 | {
501 | // Paint Shop Pro X2 and later insert an unknown block (0x21) before the start of the LayerBitmapInfo chunk.
502 | bool ok = false;
503 | if (reader.ReadUInt32() == PSPConstants.blockIdentifier)
504 | {
505 | ushort block = reader.ReadUInt16();
506 | uint length = reader.ReadUInt32();
507 |
508 | if (block == 0x21)
509 | {
510 | reader.Position += length;
511 | if (reader.ReadUInt32() == LayerBitmapInfoChunk.HeaderSize)
512 | {
513 | reader.Position -= 4L;
514 | currentOffset = reader.Position;
515 | ok = true;
516 | }
517 | }
518 | }
519 |
520 | if (!ok)
521 | {
522 | throw new FormatException(Properties.Resources.UnsupportedFormatVersion);
523 | }
524 | }
525 |
526 | rasterChunks.Add(new RasterLayerChunk(chunk, currentOffset));
527 | break;
528 | }
529 |
530 | reader.Position += (endOffset - currentOffset);
531 | }
532 | else
533 | {
534 | reader.Position += blockLength;
535 | }
536 | }
537 |
538 | return rasterChunks;
539 | }
540 | }
541 | }
542 |
--------------------------------------------------------------------------------
/src/PSPSections/PSPConstants.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | namespace PaintShopProFiletype.PSPSections
13 | {
14 | internal static class PSPConstants
15 | {
16 | ///
17 | /// Paint Shop Pro 5
18 | ///
19 | public const ushort majorVersion5 = 3;
20 | ///
21 | /// Paint Shop Pro 6
22 | ///
23 | public const ushort majorVersion6 = 4;
24 | ///
25 | /// Paint Shop Pro 7
26 | ///
27 | public const ushort majorVersion7 = 5;
28 | ///
29 | /// Paint Shop Pro 8
30 | ///
31 | public const ushort majorVersion8 = 6;
32 | ///
33 | /// Paint Shop Pro 9
34 | ///
35 | public const ushort majorVersion9 = 7;
36 | ///
37 | /// Paint Shop Pro 10
38 | ///
39 | public const ushort majorVersion10 = 8;
40 | ///
41 | /// Paint Shop Pro X2
42 | ///
43 | public const ushort majorVersion12 = 10;
44 |
45 | public const ushort v5ThumbnailBlock = 9;
46 |
47 | public const uint fieldIdentifier = 0x004c467e;
48 | public const uint blockIdentifier = 0x004b427e;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/PSPSections/PSPEnums.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | // PSP File Format Specification is Copyright (C) 2000 Jasc Software, Inc.
13 |
14 | using System;
15 |
16 | namespace PaintShopProFiletype.PSPSections
17 | {
18 | /* Block identifiers.
19 | */
20 | internal enum PSPBlockID
21 | {
22 | ImageAttributes = 0, // General Image Attributes Block (main)
23 | Creator, // Creator Data Block (main)
24 | ColorPalette, // Color Palette Block (main and sub)
25 | LayerStart, // Layer Bank Block (main)
26 | Layer, // Layer Block (sub)
27 | Channel, // Channel Block (sub)
28 | Selection, // Selection Block (main)
29 | AlphaBank, // Alpha Bank Block (main)
30 | AlphaChannel,// Alpha Channel Block (sub)
31 | CompositeImage, // Composite Image Block (sub)
32 | ExtendedData,// Extended Data Block (main)
33 | PictureTube, // Picture Tube Data Block (main)
34 | AdjustmentLayerExtension, // Adjustment Layer Block (sub)
35 | VectorLayerExtension, // Vector Layer Block (sub)
36 | VectorShape, // Vector Shape Block (sub)
37 | PaintStyle, // Paint Style Block (sub)
38 | CompositeImageBank, // Composite Image Bank (main)
39 | CompositeImageAttributes, // Composite Image Attr. (sub)
40 | JPEGImage, // JPEG Image Block (sub)
41 | LineStyle, // Line Style Block (sub)
42 | TableBank, // Table Bank Block (main)
43 | Table, // Table Block (sub)
44 | Paper, // Vector Table Paper Block (sub)
45 | Pattern, // Vector Table Pattern Block (sub)
46 | GroupLayerExtension, // Group Layer Block (sub)
47 | MaskLayerExtension, // Mask Layer Block (sub)
48 | BrushData, // Brush Data Block (main)
49 | }
50 |
51 | /* Bitmap types.
52 | */
53 | internal enum PSPDIBType
54 | {
55 | Image = 0, // Layer color bitmap
56 | TransparencyMask, // Layer transparency mask bitmap
57 | UserMask, // Layer user mask bitmap
58 | Selection, // Selection mask bitmap
59 | AlphaMask, // Alpha channel mask bitmap
60 | Thumbnail, // Thumbnail bitmap
61 | ThumbnailTransparencyMask, // Thumbnail transparency mask
62 | AdjustmentLayer, // Adjustment layer bitmap
63 | Composite, // Composite image bitmap
64 | CompositeTransparencyMask, // Composite image transparency
65 | Paper, // Paper bitmap
66 | Pattern, // Pattern bitmap
67 | PatternTransparencyMask, // Pattern transparency mask
68 | };
69 |
70 | /* Type of image in the composite image bank block.
71 | */
72 | internal enum PSPCompositeImageType
73 | {
74 | Composite = 0, // Composite Image
75 | Thumbnail, // Thumbnail Image
76 | };
77 |
78 | /* Channel types.
79 | */
80 | internal enum PSPChannelType
81 | {
82 | Composite = 0, // Channel of single channel bitmap
83 | Red, // Red channel of 24-bit bitmap
84 | Green, // Green channel of 24-bit bitmap
85 | Blue, // Blue channel of 24-bit bitmap
86 | };
87 |
88 | /* Possible types of compression.
89 | */
90 | internal enum PSPCompression
91 | {
92 | None = 0, // No compression
93 | RLE, // RLE compression
94 | LZ77, // LZ77 compression
95 | JPEG // JPEG compression (only used by thumbnail and composite image)
96 | };
97 |
98 | /* Layer types.
99 | */
100 | internal enum PSPLayerType
101 | {
102 | Undefined = 0, // Undefined layer type
103 | Raster, // Standard raster layer
104 | FloatingRasterSelection, // Floating selection (raster)
105 | Vector, // Vector layer
106 | Adjustment, // Adjustment layer
107 | }
108 |
109 | /* Layer flags.
110 | */
111 | [Flags]
112 | internal enum PSPLayerProperties
113 | {
114 | None = 0,
115 | Visible = 1, // Layer is visible
116 | MaskPresence = 2 // Layer has a mask
117 | }
118 |
119 | /* Blend modes.
120 | */
121 | internal enum PSPBlendModes
122 | {
123 | Normal,
124 | Darken,
125 | Lighten,
126 | LegacyHue,
127 | LegacySaturation,
128 | Color,
129 | LegacyLuminosity,
130 | Multiply,
131 | Screen,
132 | Dissolve,
133 | Overlay,
134 | HardLight,
135 | SoftLight,
136 | Difference,
137 | Dodge,
138 | Burn,
139 | Exclusion,
140 | TrueHue,
141 | TrueSaturation,
142 | TrueColor,
143 | TrueLightness,
144 | Adjust = 255,
145 | };
146 |
147 | /* Possible metrics used to measure resolution.
148 | */
149 | internal enum ResolutionMetric
150 | {
151 | Undefined = 0, // Metric unknown
152 | Inch, // Resolution is in inches
153 | Centimeter, // Resolution is in centimeters
154 | };
155 |
156 | /* Creator application identifiers.
157 | */
158 | internal enum PSPCreatorAppID
159 | {
160 | Unknown = 0, // Creator application unknown
161 | PaintShopPro, // Creator is Paint Shop Pro
162 | }
163 |
164 | /* Creator field types.
165 | */
166 | internal enum PSPCreatorFieldID
167 | {
168 | Title = 0, // Image document title field
169 | CreateDate, // Creation date field
170 | ModifiedDate, // Modification date field
171 | Artist, // Artist name field
172 | Copyright, // Copyright holder name field
173 | Description, // Image document description field
174 | ApplicationID, // Creating app id field
175 | ApplicationVersion, // Creating app version field
176 | }
177 |
178 | /* Extended data field types.
179 | */
180 | internal enum PSPExtendedDataID
181 | {
182 | TransparencyIndex = 0, // Transparency index field
183 | Grid, // Image grid information
184 | Guide, // Image guide information
185 | }
186 |
187 | /* Grid units type.
188 | */
189 | internal enum PSPGridUnitsType
190 | {
191 | Pixels = 0, // Grid units is pixels
192 | Inches, // Grid units is inches
193 | Centimeters // Grid units is centimeters
194 | }
195 |
196 | /* Graphic contents flags.
197 | */
198 | [Flags]
199 | internal enum PSPGraphicContents : uint
200 | {
201 | None = 0,
202 | // Layer types
203 | RasterLayers = 0x00000001, // At least one raster layer
204 | VectorLayers = 0x00000002, // At least one vector layer
205 | AdjustmentLayers = 0x00000004, // At least one adjust. layer
206 | // Additional attributes
207 | Thumbnail = 0x01000000, // Has a thumbnail
208 | ThumbnailTransparency = 0x02000000, // Thumbnail transp.
209 | Composite = 0x04000000, // Has a composite image
210 | CompositeTransparency = 0x08000000, // Composite transp.
211 | FlatImage = 0x10000000, // Just a background
212 | Selection = 0x20000000, // Has a selection
213 | FloatingSelectionLayer = 0x40000000, // Has float. selection
214 | AlphaChannels = 0x80000000 // Has alpha channel(s)
215 | }
216 |
217 | }
218 |
--------------------------------------------------------------------------------
/src/PSPSections/PSPFile.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | // Portions of this file has been adapted from:
13 | /////////////////////////////////////////////////////////////////////////////////
14 | // Paint.NET //
15 | // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
16 | // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
17 | // See src/Resources/Files/License.txt for full licensing and attribution //
18 | // details. //
19 | // . //
20 | /////////////////////////////////////////////////////////////////////////////////
21 |
22 | using CommunityToolkit.HighPerformance;
23 | using CommunityToolkit.HighPerformance.Buffers;
24 | using System;
25 | using System.Drawing;
26 | using System.IO;
27 | using System.IO.Compression;
28 | using System.Runtime.CompilerServices;
29 | using PaintDotNet;
30 | using PaintDotNet.ComponentModel;
31 | using PaintDotNet.Imaging;
32 | using PaintDotNet.Rendering;
33 | using PaintShopProFiletype.IO;
34 | using PaintShopProFiletype.PSPSections;
35 |
36 | namespace PaintShopProFiletype
37 | {
38 | internal sealed class PSPFile
39 | {
40 | private static ReadOnlySpan PSPFileSig => new byte[32]
41 | { 0x50, 0x61, 0x69, 0x6E, 0x74, 0x20, 0x53, 0x68, 0x6F, 0x70, 0x20, 0x50,
42 | 0x72, 0x6F, 0x20, 0x49, 0x6D, 0x61, 0x67, 0x65, 0x20, 0x46, 0x69, 0x6C, 0x65, 0x0A,
43 | 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00}; // "Paint Shop Pro Image File\n\x1a” padded to 32 bytes
44 |
45 | private FileHeader? fileHeader;
46 | private GeneralImageAttributes? imageAttributes;
47 | private ExtendedDataBlock? extData;
48 | private CreatorBlock? creator;
49 | private CompositeImageBlock? compImage;
50 | private ColorPaletteBlock? globalPalette;
51 | private LayerBlock? layerBlock;
52 | private ThumbnailBlock? v5Thumbnail;
53 |
54 | private readonly IServiceProvider serviceProvider;
55 |
56 | public PSPFile(IServiceProvider serviceProvider)
57 | {
58 | ArgumentNullException.ThrowIfNull(serviceProvider, nameof(serviceProvider));
59 |
60 | this.fileHeader = null;
61 | this.imageAttributes = null;
62 | this.extData = null;
63 | this.creator = null;
64 | this.compImage = null;
65 | this.globalPalette = null;
66 | this.layerBlock = null;
67 | this.v5Thumbnail = null;
68 | this.serviceProvider = serviceProvider;
69 | }
70 |
71 | [SkipLocalsInit]
72 | private static bool CheckFileSignature(EndianBinaryReader reader)
73 | {
74 | Span signature = stackalloc byte[32];
75 |
76 | reader.ReadExactly(signature);
77 |
78 | // Some writers may not write zeros for the signature padding, so we only check the first 27 bytes of the signature.
79 | const int SignatureCheckLength = 27;
80 |
81 | return signature.Slice(0, SignatureCheckLength).SequenceEqual(PSPFileSig.Slice(0, SignatureCheckLength));
82 | }
83 |
84 | private static LayerBlendMode ConvertFromPSPBlendMode(PSPBlendModes mode)
85 | {
86 | return mode switch
87 | {
88 | PSPBlendModes.Normal => LayerBlendMode.Normal,
89 | PSPBlendModes.Darken => LayerBlendMode.Darken,
90 | PSPBlendModes.Lighten => LayerBlendMode.Lighten,
91 | PSPBlendModes.Multiply => LayerBlendMode.Multiply,
92 | PSPBlendModes.Screen => LayerBlendMode.Screen,
93 | PSPBlendModes.Overlay => LayerBlendMode.Overlay,
94 | PSPBlendModes.Difference => LayerBlendMode.Difference,
95 | PSPBlendModes.Dodge => LayerBlendMode.ColorDodge,
96 | PSPBlendModes.Burn => LayerBlendMode.ColorBurn,
97 | _ => LayerBlendMode.Normal,
98 | };
99 | }
100 |
101 | private void LoadPSPFile(Stream input)
102 | {
103 | using (EndianBinaryReader reader = new EndianBinaryReader(input, Endianess.Little, leaveOpen: true))
104 | {
105 | if (!CheckFileSignature(reader))
106 | {
107 | throw new FormatException(Properties.Resources.InvalidPSPFile);
108 | }
109 |
110 | this.fileHeader = new FileHeader(reader);
111 |
112 | while (reader.Position < reader.Length)
113 | {
114 | uint blockSig = reader.ReadUInt32();
115 | if (blockSig != PSPConstants.blockIdentifier)
116 | {
117 | throw new FormatException(Properties.Resources.InvalidBlockSignature);
118 | }
119 | PSPBlockID blockID = (PSPBlockID)reader.ReadUInt16();
120 | uint initialBlockLength = this.fileHeader.Major <= PSPConstants.majorVersion5 ? reader.ReadUInt32() : 0;
121 | uint blockLength = reader.ReadUInt32();
122 |
123 | switch (blockID)
124 | {
125 | case PSPBlockID.ImageAttributes:
126 | this.imageAttributes = new GeneralImageAttributes(reader, this.fileHeader.Major);
127 | break;
128 | case PSPBlockID.Creator:
129 | this.creator = new CreatorBlock(reader, blockLength);
130 | break;
131 | case PSPBlockID.ColorPalette:
132 | this.globalPalette = new ColorPaletteBlock(reader, this.fileHeader.Major);
133 | break;
134 | case PSPBlockID.LayerStart:
135 | this.layerBlock = new LayerBlock(reader, this.imageAttributes, this.fileHeader.Major);
136 | break;
137 | case PSPBlockID.ExtendedData:
138 | this.extData = new ExtendedDataBlock(reader, blockLength);
139 | break;
140 | #if DEBUG
141 | case PSPBlockID.CompositeImageBank:
142 | this.compImage = new CompositeImageBlock(reader, this.fileHeader.Major);
143 | break;
144 | #endif
145 | default:
146 | reader.Position += blockLength;
147 | break;
148 | }
149 | }
150 | }
151 | }
152 |
153 | private static unsafe byte[] ExpandPackedPalette(Rectangle saveRect, LayerBitmapInfoChunk bitmap, int bitDepth)
154 | {
155 | int height = saveRect.Height;
156 | int width = saveRect.Width;
157 | byte[] image = new byte[width * height];
158 |
159 | int bpp, shift;
160 |
161 | switch (bitDepth)
162 | {
163 | case 4:
164 | bpp = 2;
165 | shift = 1;
166 | break;
167 | case 1:
168 | bpp = 8;
169 | shift = 3;
170 | break;
171 | default:
172 | throw new ArgumentOutOfRangeException(nameof(bitDepth), string.Format("Bit depth value of {0} is not supported, value must be 1 or 4.", bitDepth));
173 | }
174 |
175 | fixed (byte* ptr = image, dPtr = bitmap.channels![0].channelData)
176 | {
177 | int srcStride = width / bpp;
178 |
179 | for (int y = 0; y < height; y++)
180 | {
181 | byte* src = dPtr + (y * srcStride);
182 | byte* dst = ptr + (y * width);
183 |
184 | for (int x = 0; x < width; x++)
185 | {
186 | byte data = src[x >> shift];
187 |
188 | switch (bitDepth)
189 | {
190 | case 4:
191 |
192 | if ((x & 1) == 1) // odd column
193 | {
194 | *dst = (byte)(data & 0x0f);
195 | }
196 | else
197 | {
198 | *dst = (byte)(data >> 4);
199 | }
200 |
201 | break;
202 | case 1:
203 |
204 | int mask = x & 7;
205 | // extract the palette bit for the current pixel.
206 | *dst = (byte)((data & (128 >> mask)) >> (7 - mask));
207 | break;
208 | }
209 |
210 | dst++;
211 | }
212 | }
213 | }
214 |
215 | return image;
216 | }
217 |
218 | public Document Load(Stream input)
219 | {
220 | LoadPSPFile(input);
221 |
222 | if (this.imageAttributes == null || this.layerBlock == null)
223 | {
224 | throw new FormatException(Properties.Resources.InvalidPSPFile);
225 | }
226 |
227 | if (this.imageAttributes.BitDepth <= 8 && this.globalPalette == null)
228 | {
229 | throw new FormatException(Properties.Resources.ColorPaletteNotFound);
230 | }
231 |
232 | LayerInfoChunk[] infoChunks = this.layerBlock.LayerInfo;
233 | int layerCount = infoChunks.Length;
234 |
235 | // Some PSP files may have layers that are larger than the dimensions stored in the image attributes.
236 | int maxWidth = this.imageAttributes.Width;
237 | int maxHeight = this.imageAttributes.Height;
238 |
239 | for (int i = 0; i < layerCount; i++)
240 | {
241 | Rectangle savedBounds = infoChunks[i].saveRect;
242 | if (savedBounds.Width > maxWidth)
243 | {
244 | maxWidth = savedBounds.Width;
245 | }
246 | if (savedBounds.Height > maxHeight)
247 | {
248 | maxHeight = savedBounds.Height;
249 | }
250 | }
251 |
252 | if (maxWidth <= 0 || maxHeight <= 0)
253 | {
254 | throw new FormatException(Properties.Resources.InvalidDocumentDimensions);
255 | }
256 |
257 | Document doc = new Document(maxWidth, maxHeight);
258 |
259 | if (this.imageAttributes.ResValue > 0.0)
260 | {
261 | switch (this.imageAttributes.ResUnit)
262 | {
263 | case ResolutionMetric.Inch:
264 | doc.DpuUnit = MeasurementUnit.Inch;
265 | doc.DpuX = doc.DpuY = this.imageAttributes.ResValue;
266 | break;
267 | case ResolutionMetric.Centimeter:
268 | doc.DpuUnit = MeasurementUnit.Centimeter;
269 | doc.DpuX = doc.DpuY = this.imageAttributes.ResValue;
270 | break;
271 | default:
272 | break;
273 | }
274 | }
275 |
276 | short transIndex = -1;
277 |
278 | if (this.imageAttributes.BitDepth < 24 && this.extData != null)
279 | {
280 | transIndex = this.extData.TryGetTransparencyIndex();
281 | }
282 |
283 | LayerBitmapInfoChunk[] bitmapInfoChunks = this.layerBlock.LayerBitmapInfo;
284 |
285 | for (int i = 0; i < layerCount; i++)
286 | {
287 | LayerInfoChunk info = infoChunks[i];
288 |
289 | BitmapLayer layer = new BitmapLayer(doc.Width, doc.Height)
290 | {
291 | Name = info.name,
292 | Opacity = info.opacity,
293 | BlendMode = ConvertFromPSPBlendMode(info.blendMode),
294 | Visible = (info.layerFlags & PSPLayerProperties.Visible) == PSPLayerProperties.Visible
295 | };
296 |
297 | Rectangle saveRect = info.saveRect;
298 |
299 | if (!saveRect.IsEmpty)
300 | {
301 | LayerBitmapInfoChunk bitmapInfo = bitmapInfoChunks[i];
302 |
303 | if (bitmapInfo.bitmapCount == 1)
304 | {
305 | new UnaryPixelOps.SetAlphaChannelTo255().Apply(layer.Surface, saveRect);
306 | }
307 |
308 | int alphaIndex = 0;
309 |
310 | int bitDepth = this.imageAttributes.BitDepth;
311 |
312 | int bytesPerPixel = 1;
313 | int stride = saveRect.Width;
314 | byte[]? expandedPalette = null;
315 |
316 | switch (bitDepth)
317 | {
318 | case 48:
319 | bytesPerPixel = 2;
320 | stride *= 2;
321 | break;
322 | case 4:
323 | case 1:
324 | expandedPalette = ExpandPackedPalette(saveRect, bitmapInfo, bitDepth);
325 | break;
326 | }
327 |
328 | unsafe
329 | {
330 | int palIdx = 0;
331 | RGBQUAD entry;
332 |
333 | Surface surface = layer.Surface;
334 | for (int y = saveRect.Top; y < saveRect.Bottom; y++)
335 | {
336 | ColorBgra* ptr = surface.GetPointPointerUnchecked(saveRect.Left, y);
337 | ColorBgra* endPtr = ptr + saveRect.Width;
338 | int index = ((y - saveRect.Top) * stride);
339 |
340 | while (ptr < endPtr)
341 | {
342 | switch (bitDepth)
343 | {
344 | case 48:
345 |
346 | for (int ci = 0; ci < bitmapInfo.channelCount; ci++)
347 | {
348 | ChannelSubBlock ch = bitmapInfo.channels[ci];
349 |
350 | if (ch.bitmapType == PSPDIBType.Image)
351 | {
352 | ushort col = (ushort)(ch.channelData[index] | (ch.channelData[index + 1] << 8)); // PSP format is always little endian
353 | byte clamped = (byte)((col * 255) / 65535);
354 |
355 | switch (ch.channelType)
356 | {
357 | case PSPChannelType.Red:
358 | ptr->R = clamped;
359 | break;
360 | case PSPChannelType.Green:
361 | ptr->G = clamped;
362 | break;
363 | case PSPChannelType.Blue:
364 | ptr->B = clamped;
365 | break;
366 | }
367 | }
368 | else if (ch.bitmapType == PSPDIBType.TransparencyMask)
369 | {
370 | ptr->A = ch.channelData[alphaIndex];
371 | alphaIndex++;
372 | }
373 | }
374 |
375 | break;
376 | case 24:
377 |
378 | for (int ci = 0; ci < bitmapInfo.channelCount; ci++)
379 | {
380 | ChannelSubBlock ch = bitmapInfo.channels[ci];
381 |
382 | if (ch.bitmapType == PSPDIBType.Image)
383 | {
384 | switch (ch.channelType)
385 | {
386 | case PSPChannelType.Red:
387 | ptr->R = ch.channelData[index];
388 | break;
389 | case PSPChannelType.Green:
390 | ptr->G = ch.channelData[index];
391 | break;
392 | case PSPChannelType.Blue:
393 | ptr->B = ch.channelData[index];
394 | break;
395 | }
396 | }
397 | else if (ch.bitmapType == PSPDIBType.TransparencyMask)
398 | {
399 | ptr->A = ch.channelData[index];
400 | }
401 | }
402 |
403 | break;
404 | case 8:
405 | for (int ci = 0; ci < bitmapInfo.channelCount; ci++)
406 | {
407 | ChannelSubBlock ch = bitmapInfo.channels[ci];
408 | palIdx = ch.channelData[index];
409 | entry = this.globalPalette!.entries[palIdx];
410 |
411 | switch (ch.bitmapType)
412 | {
413 | case PSPDIBType.Image:
414 | ptr->R = entry.red;
415 | ptr->G = entry.green;
416 | ptr->B = entry.blue;
417 |
418 | if (palIdx == transIndex)
419 | {
420 | ptr->A = 0;
421 | }
422 |
423 | break;
424 | case PSPDIBType.TransparencyMask:
425 | ptr->A = entry.red;
426 | break;
427 | }
428 | }
429 |
430 | break;
431 | case 4:
432 | case 1:
433 |
434 | palIdx = expandedPalette![index];
435 | entry = this.globalPalette!.entries[palIdx];
436 |
437 | ptr->R = entry.red;
438 | ptr->G = entry.green;
439 | ptr->B = entry.blue;
440 |
441 | if (palIdx == transIndex)
442 | {
443 | ptr->A = 0;
444 | }
445 | else if ((bitmapInfo.bitmapCount == 2) && bitmapInfo.channels[1].bitmapType == PSPDIBType.TransparencyMask)
446 | {
447 | ptr->A = bitmapInfo.channels[1].channelData[index];
448 | }
449 |
450 | break;
451 | default:
452 | throw new FormatException(string.Format(Properties.Resources.UnsupportedBitDepth, bitDepth));
453 |
454 | }
455 |
456 | ptr++;
457 | index += bytesPerPixel;
458 | }
459 | }
460 | }
461 | }
462 |
463 | #if DEBUG
464 | using (Bitmap temp = layer.Surface.CreateAliasedBitmap())
465 | {
466 | }
467 | #endif
468 | doc.Layers.Add(layer);
469 | }
470 |
471 | CreatorBlockSerialization.Serialize(this.creator, doc);
472 |
473 | return doc;
474 | }
475 |
476 | private static PSPBlendModes ConvertToPSPBlendMode(LayerBlendMode blendMode)
477 | {
478 | return blendMode switch
479 | {
480 | LayerBlendMode.Normal => PSPBlendModes.Normal,
481 | LayerBlendMode.Multiply => PSPBlendModes.Multiply,
482 | LayerBlendMode.ColorBurn => PSPBlendModes.Burn,
483 | LayerBlendMode.ColorDodge => PSPBlendModes.Dodge,
484 | LayerBlendMode.Overlay => PSPBlendModes.Overlay,
485 | LayerBlendMode.Difference => PSPBlendModes.Difference,
486 | LayerBlendMode.Lighten => PSPBlendModes.Lighten,
487 | LayerBlendMode.Darken => PSPBlendModes.Darken,
488 | LayerBlendMode.Screen => PSPBlendModes.Screen,
489 | _ => PSPBlendModes.Normal,
490 | };
491 | }
492 |
493 | private static PSPCompression CompressionFromTokenFormat(CompressionFormats format)
494 | {
495 | PSPCompression comp = PSPCompression.None;
496 |
497 | switch (format)
498 | {
499 | case CompressionFormats.LZ77:
500 | comp = PSPCompression.LZ77;
501 | break;
502 | }
503 |
504 | return comp;
505 | }
506 |
507 | private int doneProgress;
508 | private int totalProgress;
509 | private unsafe ChannelSubBlock[] SplitImageChannels(
510 | Surface source,
511 | Rectangle savedBounds,
512 | int channelCount,
513 | ushort majorVersion,
514 | bool composite,
515 | ProgressEventHandler callback)
516 | {
517 | ChannelSubBlock[] channels = new ChannelSubBlock[channelCount];
518 |
519 | int channelSize = checked(savedBounds.Width * savedBounds.Height);
520 |
521 | for (int i = 0; i < channelCount; i++)
522 | {
523 | channels[i] = new ChannelSubBlock(majorVersion, (uint)channelSize);
524 | if (composite)
525 | {
526 | channels[i].bitmapType = majorVersion switch
527 | {
528 | PSPConstants.majorVersion5 => PSPDIBType.Thumbnail,
529 | _ => i < 3 ? PSPDIBType.Composite : PSPDIBType.CompositeTransparencyMask,
530 | };
531 | }
532 | else
533 | {
534 | channels[i].bitmapType = i < 3 ? PSPDIBType.Image : PSPDIBType.TransparencyMask;
535 | }
536 |
537 | switch (i)
538 | {
539 | case 0:
540 | channels[i].channelType = PSPChannelType.Red;
541 | break;
542 | case 1:
543 | channels[i].channelType = PSPChannelType.Green;
544 | break;
545 | case 2:
546 | channels[i].channelType = PSPChannelType.Blue;
547 | break;
548 | case 3:
549 | channels[i].channelType = PSPChannelType.Composite;
550 | break;
551 | }
552 | }
553 |
554 | if (channelSize > 0)
555 | {
556 | RegionPtr sourceRegion = source.AsRegionPtr().Slice(savedBounds).Cast();
557 |
558 | using (SpanOwner uncompressedDataOwner = SpanOwner.Allocate(channelSize))
559 | {
560 | Span uncompresedData = uncompressedDataOwner.Span;
561 |
562 | fixed (byte* buffer = uncompresedData)
563 | {
564 | RegionPtr targetRegion = new RegionPtr(buffer, savedBounds.Size, savedBounds.Width);
565 |
566 | for (int channelIndex = 0; channelIndex < channelCount; channelIndex++)
567 | {
568 | int sourceIndex = channelIndex;
569 |
570 | // Map BGRA to RGBA
571 | switch (channelIndex)
572 | {
573 | case 0:
574 | sourceIndex = 2;
575 | break;
576 | case 2:
577 | sourceIndex = 0;
578 | break;
579 | }
580 |
581 | PixelKernels.ExtractChannel(targetRegion, sourceRegion, sourceIndex);
582 |
583 | ChannelSubBlock channel = channels[channelIndex];
584 |
585 | switch (this.imageAttributes!.CompressionType)
586 | {
587 | case PSPCompression.None:
588 | channel.compressedChannelLength = (uint)channelSize;
589 | channel.uncompressedChannelLength = 0;
590 | channel.channelData = uncompresedData.ToArray();
591 | break;
592 | case PSPCompression.LZ77:
593 |
594 | using (ArrayPoolBufferWriter bufferWriter = new ArrayPoolBufferWriter())
595 | {
596 | using (ZLibStream zs = new ZLibStream(bufferWriter.AsStream(), CompressionLevel.SmallestSize, true))
597 | {
598 | zs.Write(uncompresedData);
599 | }
600 |
601 | ReadOnlySpan compressedData = bufferWriter.WrittenSpan;
602 | channel.compressedChannelLength = (uint)compressedData.Length;
603 | channel.uncompressedChannelLength = (uint)channelSize;
604 | channel.channelData = compressedData.ToArray();
605 | }
606 | break;
607 | }
608 |
609 | if (callback != null)
610 | {
611 | this.doneProgress++;
612 |
613 | callback(this, new ProgressEventArgs(100.0 * ((double)this.doneProgress / this.totalProgress)));
614 | }
615 | }
616 | }
617 | }
618 | }
619 |
620 | return channels;
621 | }
622 |
623 | private static Size GetThumbnailDimensions(int originalWidth, int originalHeight, int maxEdgeLength)
624 | {
625 | Size thumbSize = Size.Empty;
626 |
627 | if (originalWidth <= 0 || originalHeight <= 0)
628 | {
629 | thumbSize.Width = 1;
630 | thumbSize.Height = 1;
631 | }
632 | else if (originalWidth > originalHeight)
633 | {
634 | int longSide = Math.Min(originalWidth, maxEdgeLength);
635 | thumbSize.Width = longSide;
636 | thumbSize.Height = Math.Max(1, (originalHeight * longSide) / originalWidth);
637 | }
638 | else if (originalHeight > originalWidth)
639 | {
640 | int longSide = Math.Min(originalHeight, maxEdgeLength);
641 | thumbSize.Width = Math.Max(1, (originalWidth * longSide) / originalHeight);
642 | thumbSize.Height = longSide;
643 | }
644 | else
645 | {
646 | int longSide = Math.Min(originalWidth, maxEdgeLength);
647 | thumbSize.Width = longSide;
648 | thumbSize.Height = longSide;
649 | }
650 |
651 | return thumbSize;
652 | }
653 |
654 | private static unsafe bool LayerHasTransparency(Surface surface, Rectangle bounds)
655 | {
656 | for (int y = bounds.Top; y < bounds.Bottom; y++)
657 | {
658 | ColorBgra* startPtr = surface.GetPointPointerUnchecked(bounds.Left, y);
659 | ColorBgra* endPtr = startPtr + bounds.Width;
660 |
661 | while (startPtr < endPtr)
662 | {
663 | if (startPtr->A < 255)
664 | {
665 | return true;
666 | }
667 |
668 | startPtr++;
669 | }
670 | }
671 |
672 | return false;
673 | }
674 |
675 | public void Save(Document input, Stream output, CompressionFormats format, Surface scratchSurface, ushort majorVersion, ProgressEventHandler callback)
676 | {
677 | if (majorVersion == PSPConstants.majorVersion5 && input.Layers.Count > 64)
678 | {
679 | throw new FormatException(string.Format(Properties.Resources.MaxLayersFormat, 64));
680 | }
681 | else if ((majorVersion == PSPConstants.majorVersion6 || majorVersion == PSPConstants.majorVersion7) && input.Layers.Count > 100)
682 | {
683 | throw new FormatException(string.Format(Properties.Resources.MaxLayersFormat, 100));
684 | }
685 |
686 | using (BinaryWriterEx writer = new BinaryWriterEx(output, false))
687 | {
688 | this.fileHeader = new FileHeader(majorVersion);
689 | this.imageAttributes = new GeneralImageAttributes(
690 | input.Width,
691 | input.Height,
692 | CompressionFromTokenFormat(format),
693 | 0,
694 | input.Layers.Count,
695 | majorVersion);
696 | switch (input.DpuUnit)
697 | {
698 | case MeasurementUnit.Centimeter:
699 | this.imageAttributes.ResValue = input.DpuX;
700 | this.imageAttributes.ResUnit = ResolutionMetric.Centimeter;
701 | break;
702 | case MeasurementUnit.Inch:
703 | this.imageAttributes.ResValue = input.DpuX;
704 | this.imageAttributes.ResUnit = ResolutionMetric.Inch;
705 | break;
706 | }
707 |
708 | this.totalProgress = 0;
709 | this.doneProgress = 0;
710 |
711 | bool flatImage = true;
712 | foreach (Layer item in input.Layers)
713 | {
714 | BitmapLayer layer = (BitmapLayer)item;
715 |
716 | Rectangle rect = PSPUtil.GetImageSaveRectangle(layer.Surface);
717 |
718 | if (!rect.IsEmpty)
719 | {
720 | if (LayerHasTransparency(layer.Surface, rect))
721 | {
722 | this.totalProgress += 4;
723 | flatImage = false;
724 | }
725 | else
726 | {
727 | this.totalProgress += 3;
728 | }
729 | }
730 |
731 | }
732 |
733 | if (flatImage)
734 | {
735 | this.imageAttributes.SetGraphicContentFlag(PSPGraphicContents.FlatImage);
736 | }
737 |
738 | if (majorVersion > PSPConstants.majorVersion5)
739 | {
740 | CreateCompositeImageBlock(input, scratchSurface, callback, majorVersion);
741 | }
742 | else
743 | {
744 | CreateThumbnailBlock(input, scratchSurface, callback, majorVersion);
745 | }
746 |
747 | int layerCount = input.Layers.Count;
748 |
749 | LayerInfoChunk[] layerInfoChunks = new LayerInfoChunk[layerCount];
750 | LayerBitmapInfoChunk[] layerBitmapChunks = new LayerBitmapInfoChunk[layerCount];
751 |
752 | for (int i = 0; i < layerCount; i++)
753 | {
754 | BitmapLayer layer = (BitmapLayer)input.Layers[i];
755 |
756 | Rectangle savedBounds = PSPUtil.GetImageSaveRectangle(layer.Surface);
757 |
758 | LayerInfoChunk infoChunk = new LayerInfoChunk(layer, ConvertToPSPBlendMode(layer.BlendMode), savedBounds, majorVersion);
759 |
760 | int channelCount = 3;
761 | int bitmapCount = 1;
762 |
763 | if (LayerHasTransparency(layer.Surface, savedBounds))
764 | {
765 | channelCount = 4;
766 | bitmapCount = 2;
767 | }
768 |
769 | LayerBitmapInfoChunk biChunk = new LayerBitmapInfoChunk(bitmapCount, SplitImageChannels(layer.Surface,
770 | savedBounds,
771 | channelCount,
772 | majorVersion,
773 | false,
774 | callback));
775 |
776 | if (majorVersion <= PSPConstants.majorVersion5)
777 | {
778 | infoChunk.v5BitmapCount = biChunk.bitmapCount;
779 | infoChunk.v5ChannelCount = biChunk.channelCount;
780 | }
781 |
782 | layerInfoChunks[i] = infoChunk;
783 | layerBitmapChunks[i] = biChunk;
784 | }
785 |
786 | this.layerBlock = new LayerBlock(layerInfoChunks, layerBitmapChunks);
787 | this.creator = CreatorBlockSerialization.Deserialize(input);
788 |
789 | writer.Write(PSPFileSig);
790 | this.fileHeader.Save(writer);
791 | this.imageAttributes.Save(writer);
792 | this.creator?.Save(writer, majorVersion);
793 | if (this.compImage != null)
794 | {
795 | this.compImage.Save(writer);
796 | }
797 | else
798 | {
799 | this.v5Thumbnail?.Save(writer);
800 | }
801 | this.layerBlock.Save(writer, majorVersion);
802 | }
803 | }
804 |
805 | private void CreateCompositeImageBlock(Document input, Surface scratchSurface, ProgressEventHandler callback, ushort majorVersion)
806 | {
807 | Size jpegThumbSize = GetThumbnailDimensions(input.Width, input.Height, 200);
808 |
809 | CompositeImageAttributesChunk normAttr = new CompositeImageAttributesChunk(
810 | input.Width,
811 | input.Height,
812 | PSPCompositeImageType.Composite,
813 | this.imageAttributes!.CompressionType);
814 | CompositeImageAttributesChunk jpgAttr = new CompositeImageAttributesChunk(
815 | jpegThumbSize.Width,
816 | jpegThumbSize.Height,
817 | PSPCompositeImageType.Thumbnail,
818 | PSPCompression.JPEG);
819 | JPEGCompositeInfoChunk jpgChunk = new JPEGCompositeInfoChunk();
820 | CompositeImageInfoChunk infoChunk = new CompositeImageInfoChunk();
821 |
822 | scratchSurface.Fill(ColorBgra.TransparentBlack);
823 | input.CreateRenderer().Render(scratchSurface);
824 |
825 | int channelCount = 3;
826 | if (LayerHasTransparency(scratchSurface, scratchSurface.Bounds))
827 | {
828 | channelCount = 4;
829 | infoChunk.channelCount = 4;
830 | infoChunk.bitmapCount = 2;
831 | }
832 | #if DEBUG
833 | using (Bitmap bmp = scratchSurface.CreateAliasedBitmap())
834 | {
835 | }
836 | #endif
837 |
838 | this.totalProgress += channelCount;
839 |
840 | infoChunk.channelBlocks = SplitImageChannels(scratchSurface, scratchSurface.Bounds, channelCount, majorVersion, true, callback);
841 |
842 | jpgChunk.imageData = GetJpegThumbnailData(jpegThumbSize, scratchSurface, channelCount);
843 | jpgChunk.compressedSize = (uint)jpgChunk.imageData.Length;
844 |
845 | this.imageAttributes.SetGraphicContentFlag(PSPGraphicContents.Composite);
846 | this.imageAttributes.SetGraphicContentFlag(PSPGraphicContents.Thumbnail);
847 |
848 | if (infoChunk.bitmapCount == 2)
849 | {
850 | this.imageAttributes.SetGraphicContentFlag(PSPGraphicContents.CompositeTransparency);
851 | }
852 |
853 | this.compImage = new CompositeImageBlock(new CompositeImageAttributesChunk[2] { jpgAttr, normAttr }, jpgChunk, infoChunk);
854 | }
855 |
856 | private void CreateThumbnailBlock(Document input, Surface scratchSurface, ProgressEventHandler callback, ushort majorVersion)
857 | {
858 | Size thumbSize = GetThumbnailDimensions(input.Width, input.Height, 300);
859 |
860 | scratchSurface.Fill(ColorBgra.White);
861 | input.CreateRenderer().Render(scratchSurface);
862 |
863 | using (Surface fit = new Surface(thumbSize))
864 | {
865 | fit.FitSurface(ResamplingAlgorithm.SuperSampling, scratchSurface);
866 |
867 | this.totalProgress += 3;
868 |
869 | ChannelSubBlock[] channels = SplitImageChannels(scratchSurface, scratchSurface.Bounds, 3, majorVersion, true, callback);
870 | this.v5Thumbnail = new ThumbnailBlock(thumbSize.Width, thumbSize.Height, channels);
871 | }
872 | }
873 |
874 | private byte[] GetJpegThumbnailData(Size jpegThumbSize, Surface scratchSurface, int channelCount)
875 | {
876 | IImagingFactory? imagingFactory = this.serviceProvider.GetService();
877 |
878 | #pragma warning disable IDE0270 // Use coalesce expression
879 | if (imagingFactory is null)
880 | {
881 | throw new InvalidOperationException($"Failed to get the {nameof(IImagingFactory)} for saving the image thumbnail.");
882 | }
883 | #pragma warning restore IDE0270 // Use coalesce expression
884 |
885 | int width = jpegThumbSize.Width;
886 | int height = jpegThumbSize.Height;
887 |
888 | byte[] jpegThumbnailData;
889 |
890 | using (Surface fit = new Surface(width, height))
891 | {
892 | fit.FitSurface(ResamplingAlgorithm.AdaptiveHighQuality, scratchSurface);
893 |
894 | if (channelCount == 4)
895 | {
896 | unsafe
897 | {
898 | for (int y = 0; y < height; y++)
899 | {
900 | ColorBgra* ptr = fit.GetRowPointerUnchecked(y);
901 | ColorBgra* endPtr = ptr + width;
902 |
903 | while (ptr < endPtr)
904 | {
905 | if (ptr->A == 0)
906 | {
907 | ptr->Bgra |= 0x00ffffff; // set the color of the transparent pixels to white, same as Paint Shop Pro
908 | }
909 |
910 | ptr++;
911 | }
912 | }
913 | }
914 | }
915 |
916 | using (MemoryStream stream = new MemoryStream())
917 | {
918 | using (IBitmap sharedBitmap = fit.CreateSharedBitmap())
919 | using (IBitmapSource convertedBitmap = sharedBitmap.CreateFormatConverter())
920 | using (IBitmapEncoder encoder = imagingFactory.CreateEncoder(stream, ContainerFormats.Jpeg))
921 | using (IBitmapFrameEncode frameEncode = encoder.CreateNewFrame(out IPropertyBag2 encoderOptions))
922 | {
923 | frameEncode.Initialize(encoderOptions);
924 |
925 | frameEncode.SetSize(width, height);
926 | frameEncode.SetPixelFormat(PixelFormats.Bgr24);
927 | frameEncode.WriteSource(convertedBitmap);
928 |
929 | frameEncode.Commit();
930 | encoder.Commit();
931 | }
932 |
933 | jpegThumbnailData = stream.ToArray();
934 | }
935 | }
936 |
937 | return jpegThumbnailData;
938 | }
939 | }
940 | }
941 |
--------------------------------------------------------------------------------
/src/PSPSections/PSPUtil.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | // Portions of this file has been adapted from:
13 | /////////////////////////////////////////////////////////////////////////////////
14 | //
15 | // Photoshop PSD FileType Plugin for Paint.NET
16 | // http://psdplugin.codeplex.com/
17 | //
18 | // This software is provided under the MIT License:
19 | // Copyright (c) 2006-2007 Frank Blumenberg
20 | // Copyright (c) 2010-2013 Tao Yue
21 | //
22 | // See LICENSE.txt for complete licensing and attribution information.
23 | //
24 | /////////////////////////////////////////////////////////////////////////////////
25 |
26 | using System;
27 | using System.Drawing;
28 | using System.Globalization;
29 | using PaintDotNet;
30 |
31 | namespace PaintShopProFiletype.PSPSections
32 | {
33 | internal static partial class PSPUtil
34 | {
35 | [System.Diagnostics.DebuggerDisplay("Top = {Top}, Bottom = {Bottom}, Left = {Left}, Right = {Right}")]
36 | private struct RectanglePosition
37 | {
38 | public int Top { get; set; }
39 | public int Bottom { get; set; }
40 | public int Left { get; set; }
41 | public int Right { get; set; }
42 | }
43 |
44 | public static unsafe Rectangle GetImageSaveRectangle(Surface surface)
45 | {
46 | RectanglePosition rectPos = new RectanglePosition()
47 | {
48 | Left = surface.Width,
49 | Top = surface.Height,
50 | Right = 0,
51 | Bottom = 0
52 | };
53 |
54 | // Search for top non-transparent pixel
55 | bool fPixelFound = false;
56 | for (int y = 0; y < surface.Height; y++)
57 | {
58 | if (ExpandImageRectangle(surface, y, 0, surface.Width, ref rectPos))
59 | {
60 | fPixelFound = true;
61 | break;
62 | }
63 | }
64 |
65 | // Narrow down the other dimensions of the image rectangle
66 | if (fPixelFound)
67 | {
68 | // Search for bottom non-transparent pixel
69 | for (int y = surface.Height - 1; y > rectPos.Bottom; y--)
70 | {
71 | if (ExpandImageRectangle(surface, y, 0, surface.Width, ref rectPos))
72 | {
73 | break;
74 | }
75 | }
76 |
77 | // Search for left and right non-transparent pixels. Because we
78 | // scan horizontally, we can't just break, but we can examine fewer
79 | // candidate pixels on the remaining rows.
80 | for (int y = rectPos.Top + 1; y < rectPos.Bottom; y++)
81 | {
82 | ExpandImageRectangle(surface, y, 0, rectPos.Left, ref rectPos);
83 | ExpandImageRectangle(surface, y, rectPos.Right + 1, surface.Width, ref rectPos);
84 | }
85 | }
86 | else
87 | {
88 | rectPos.Left = 0;
89 | rectPos.Top = 0;
90 | }
91 |
92 | if ((rectPos.Left < rectPos.Right) && (rectPos.Top < rectPos.Bottom))
93 | {
94 | return new Rectangle(rectPos.Left, rectPos.Top, rectPos.Right - rectPos.Left + 1, rectPos.Bottom - rectPos.Top + 1);
95 | }
96 |
97 | return Rectangle.Empty;
98 | }
99 |
100 | ///
101 | /// Check for non-transparent pixels in a row, or portion of a row.
102 | /// Expands the size of the image rectangle if any were found.
103 | ///
104 | /// True if non-transparent pixels were found, false otherwise.
105 | unsafe private static bool ExpandImageRectangle(Surface surface, int y,
106 | int xStart, int xEnd, ref RectanglePosition rectPos)
107 | {
108 | bool fPixelFound = false;
109 |
110 | ColorBgra* rowStart = surface.GetRowPointer(y);
111 | ColorBgra* pixel = rowStart + xStart;
112 | for (int x = xStart; x < xEnd; x++)
113 | {
114 | if (pixel->A > 0)
115 | {
116 | // Expand the rectangle to include the specified point.
117 | if (x < rectPos.Left)
118 | {
119 | rectPos.Left = x;
120 | }
121 |
122 | if (x > rectPos.Right)
123 | {
124 | rectPos.Right = x;
125 | }
126 |
127 | if (y < rectPos.Top)
128 | {
129 | rectPos.Top = y;
130 | }
131 |
132 | if (y > rectPos.Bottom)
133 | {
134 | rectPos.Bottom = y;
135 | }
136 |
137 | fPixelFound = true;
138 | }
139 | pixel++;
140 | }
141 |
142 | return fPixelFound;
143 | }
144 |
145 | ///
146 | /// Checks that the block type matches the expected .
147 | ///
148 | /// The block identifier.
149 | /// The expected .
150 | internal static void CheckBlockType(ushort blockID, PSPBlockID expected)
151 | {
152 | if (blockID != (ushort)expected)
153 | {
154 | throw new FormatException(string.Format(Properties.Resources.UnexpectedBlockTypeFormat,
155 | blockID.ToString(CultureInfo.InvariantCulture),
156 | ((ushort)expected).ToString(CultureInfo.InvariantCulture),
157 | expected.ToString()));
158 | }
159 | }
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/PSPSections/RGBQUAD.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using System.Runtime.InteropServices;
13 |
14 | namespace PaintShopProFiletype.PSPSections
15 | {
16 | [StructLayout(LayoutKind.Sequential)]
17 | internal readonly struct RGBQUAD
18 | {
19 | public readonly byte red;
20 | public readonly byte green;
21 | public readonly byte blue;
22 | public readonly byte reserved;
23 |
24 | public RGBQUAD(IO.EndianBinaryReader reader)
25 | {
26 | this.red = reader.ReadByte();
27 | this.green = reader.ReadByte();
28 | this.blue = reader.ReadByte();
29 | this.reserved = reader.ReadByte();
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/PSPSections/RLE.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using System;
13 |
14 | namespace PaintShopProFiletype.PSPSections
15 | {
16 | internal static class RLE
17 | {
18 | public static void Decompress(ReadOnlySpan compressedData, Span uncompressedData)
19 | {
20 | int srcIndx = 0;
21 | int dstIndx = 0;
22 |
23 | int len;
24 | byte value;
25 | int compressedLength = compressedData.Length;
26 | int uncompressedLength = uncompressedData.Length;
27 |
28 | while (srcIndx < compressedLength && dstIndx < uncompressedLength)
29 | {
30 | len = compressedData[srcIndx++];
31 |
32 | if (len > 128)
33 | {
34 | len -= 128;
35 | value = compressedData[srcIndx++];
36 |
37 | uncompressedData.Slice(dstIndx, len).Fill(value);
38 | }
39 | else
40 | {
41 | compressedData.Slice(srcIndx, len).CopyTo(uncompressedData.Slice(dstIndx, len));
42 | }
43 |
44 | dstIndx += len;
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/PSPSections/ThumbnailBlock.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using PaintShopProFiletype.IO;
13 |
14 | namespace PaintShopProFiletype.PSPSections
15 | {
16 | ///
17 | /// Only present in PSP 5 (or earlier?) files
18 | ///
19 | internal sealed class ThumbnailBlock
20 | {
21 | internal int width;
22 | internal int height;
23 | internal ushort bitDepth;
24 | internal PSPCompression compressionType; // ushort
25 | internal ushort planeCount;
26 | internal uint colorCount;
27 | internal uint paletteEntryCount;
28 | internal ColorPaletteBlock? paletteSubBlock;
29 | internal ushort channelCount;
30 | internal ChannelSubBlock[] channelBlocks;
31 |
32 | private const uint InitalBlockLength = 24U;
33 |
34 | public ThumbnailBlock(int width, int height, ChannelSubBlock[] channels)
35 | {
36 | this.width = width;
37 | this.height = height;
38 | this.bitDepth = 24;
39 | this.compressionType = PSPCompression.LZ77;
40 | this.planeCount = 1;
41 | this.colorCount = (1 << 24);
42 | this.paletteEntryCount = 0;
43 | this.paletteSubBlock = null;
44 | this.channelCount = checked((ushort)channels.Length);
45 | this.channelBlocks = channels;
46 | }
47 |
48 | #if DEBUG
49 | public ThumbnailBlock(EndianBinaryReader br)
50 | {
51 | this.width = br.ReadInt32();
52 | this.height = br.ReadInt32();
53 | this.bitDepth = br.ReadUInt16();
54 | this.compressionType = (PSPCompression)br.ReadUInt16();
55 | this.planeCount = br.ReadUInt16();
56 | this.colorCount = br.ReadUInt32();
57 | this.paletteEntryCount = br.ReadUInt32();
58 | this.channelCount = br.ReadUInt16();
59 |
60 | this.channelBlocks = new ChannelSubBlock[this.channelCount];
61 |
62 | int index = 0;
63 | do
64 | {
65 | uint blockSig = br.ReadUInt32();
66 | if (blockSig != PSPConstants.blockIdentifier)
67 | {
68 | throw new System.FormatException(Properties.Resources.InvalidBlockSignature);
69 | }
70 | PSPBlockID blockType = (PSPBlockID)br.ReadUInt16();
71 | #pragma warning disable IDE0059 // Value assigned to symbol is never used
72 | uint chunkLength = br.ReadUInt32();
73 | #pragma warning restore IDE0059 // Value assigned to symbol is never used
74 |
75 | switch (blockType)
76 | {
77 | case PSPBlockID.ColorPalette:
78 | this.paletteSubBlock = new ColorPaletteBlock(br, PSPConstants.majorVersion5);
79 | break;
80 | case PSPBlockID.Channel:
81 | ChannelSubBlock block = new ChannelSubBlock(br, this.compressionType, PSPConstants.majorVersion5);
82 | this.channelBlocks[index] = block;
83 | index++;
84 | break;
85 | }
86 | }
87 | while (index < this.channelCount);
88 | }
89 | #endif
90 |
91 | public void Save(BinaryWriterEx bw)
92 | {
93 | bw.Write(PSPConstants.blockIdentifier);
94 | bw.Write(PSPConstants.v5ThumbnailBlock);
95 | bw.Write(InitalBlockLength);
96 |
97 | using (new BlockLengthWriter(bw))
98 | {
99 | bw.Write(this.width);
100 | bw.Write(this.height);
101 | bw.Write(this.bitDepth);
102 | bw.Write((ushort)this.compressionType);
103 | bw.Write(this.planeCount);
104 | bw.Write(this.colorCount);
105 | bw.Write(this.paletteEntryCount);
106 | bw.Write(this.channelCount);
107 |
108 | for (int i = 0; i < this.channelCount; i++)
109 | {
110 | this.channelBlocks![i].Save(bw, PSPConstants.majorVersion5);
111 | }
112 | }
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/PaintShopProFiletype.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net7.0-windows
4 | Library
5 | false
6 | true
7 | true
8 | true
9 | enable
10 |
11 |
12 | true
13 | AllRules.ruleset
14 |
15 |
16 | none
17 | true
18 | AllRules.ruleset
19 |
20 |
21 |
22 | ..\..\..\..\..\..\Program Files\paint.net\PaintDotNet.Base.dll
23 |
24 |
25 | ..\..\..\..\..\..\Program Files\paint.net\PaintDotNet.ComponentModel.dll
26 |
27 |
28 | ..\..\..\..\..\..\Program Files\paint.net\PaintDotNet.Core.dll
29 |
30 |
31 | ..\..\..\..\..\..\Program Files\paint.net\PaintDotNet.Data.dll
32 |
33 |
34 | ..\..\..\..\..\..\Program Files\paint.net\PaintDotNet.Fundamentals.dll
35 |
36 |
37 | ..\..\..\..\..\..\Program Files\paint.net\PaintDotNet.Primitives.dll
38 |
39 |
40 | ..\..\..\..\..\..\Program Files\paint.net\PaintDotNet.PropertySystem.dll
41 |
42 |
43 | ..\..\..\..\..\..\Program Files\paint.net\PaintDotNet.Windows.dll
44 |
45 |
46 | ..\..\..\..\..\..\Program Files\paint.net\PaintDotNet.Windows.Core.dll
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | True
55 | True
56 | Resources.resx
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/PaintShopProFiletype.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26430.16
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PaintShopProFiletype", "PaintShopProFiletype.csproj", "{C54DB656-EB36-4BC0-A5E5-DAB04982341D}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {C54DB656-EB36-4BC0-A5E5-DAB04982341D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {C54DB656-EB36-4BC0-A5E5-DAB04982341D}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {C54DB656-EB36-4BC0-A5E5-DAB04982341D}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {C54DB656-EB36-4BC0-A5E5-DAB04982341D}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | EndGlobal
23 |
--------------------------------------------------------------------------------
/src/PluginSupportInfo.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using System;
13 | using System.Reflection;
14 | using PaintDotNet;
15 |
16 | namespace PaintShopProFiletype
17 | {
18 | public sealed class PluginSupportInfo : IPluginSupportInfo
19 | {
20 | public string Author
21 | {
22 | get
23 | {
24 | return "null54";
25 | }
26 | }
27 |
28 | public string Copyright
29 | {
30 | get
31 | {
32 | return ((AssemblyCopyrightAttribute)typeof(PaintShopProFormat).Assembly.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false)[0]).Copyright;
33 | }
34 | }
35 |
36 | public string DisplayName
37 | {
38 | get
39 | {
40 | return "Paint Shop Pro Filetype";
41 | }
42 | }
43 |
44 | public Version Version
45 | {
46 | get
47 | {
48 | return typeof(PaintShopProFormat).Assembly.GetName().Version!;
49 | }
50 | }
51 |
52 | public Uri WebsiteUri
53 | {
54 | get
55 | {
56 | return new Uri("https://forums.getpaint.net/index.php?showtopic=22817");
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // This file is part of pdn-pspformat, a FileType plugin for Paint.NET
4 | //
5 | // Copyright (c) 2011-2015, 2019, 2023 Nicholas Hayes
6 | //
7 | // This file is licensed under the MIT License.
8 | // See LICENSE.txt for complete licensing and attribution information.
9 | //
10 | ////////////////////////////////////////////////////////////////////////
11 |
12 | using System.Reflection;
13 | using System.Runtime.InteropServices;
14 | using System.Runtime.Versioning;
15 |
16 | // General Information about an assembly is controlled through the following
17 | // set of attributes. Change these attribute values to modify the information
18 | // associated with an assembly.
19 | [assembly: AssemblyTitle("Paint Shop Pro® Filetype plugin for Paint.NET")]
20 | [assembly: AssemblyDescription("")]
21 | [assembly: AssemblyConfiguration("")]
22 | [assembly: AssemblyCompany("null54")]
23 | [assembly: AssemblyProduct("PaintShopProFiletype")]
24 | [assembly: AssemblyCopyright("Copyright © 2023 Nicholas Hayes (aka null54)")]
25 | [assembly: AssemblyTrademark("")]
26 | [assembly: AssemblyCulture("")]
27 |
28 | // Setting ComVisible to false makes the types in this assembly not visible
29 | // to COM components. If you need to access a type in this assembly from
30 | // COM, set the ComVisible attribute to true on that type.
31 | [assembly: ComVisible(false)]
32 |
33 | // The following GUID is for the ID of the typelib if this project is exposed to COM
34 | [assembly: Guid("bfd6dfcb-8e94-4f95-b840-91f636399fa5")]
35 |
36 | [assembly: SupportedOSPlatform("windows")]
37 |
38 | // Version information for an assembly consists of the following four values:
39 | //
40 | // Major Version
41 | // Minor Version
42 | // Build Number
43 | // Revision
44 | //
45 | // You can specify all the values or you can default the Build and Revision Numbers
46 | // by using the '*' as shown below:
47 | // [assembly: AssemblyVersion("1.0.*")]
48 | [assembly: AssemblyVersion("1.0.3.0")]
49 | [assembly: AssemblyFileVersion("1.0.3.0")]
50 |
--------------------------------------------------------------------------------
/src/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.34209
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace PaintShopProFiletype.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PaintShopProFiletype.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized string similar to The file does not contain a color palette..
65 | ///
66 | internal static string ColorPaletteNotFound {
67 | get {
68 | return ResourceManager.GetString("ColorPaletteNotFound", resourceCulture);
69 | }
70 | }
71 |
72 | ///
73 | /// Looks up a localized string similar to LZ77.
74 | ///
75 | internal static string CompressionFormatLZ77Text {
76 | get {
77 | return ResourceManager.GetString("CompressionFormatLZ77Text", resourceCulture);
78 | }
79 | }
80 |
81 | ///
82 | /// Looks up a localized string similar to None.
83 | ///
84 | internal static string CompressionFormatNoneText {
85 | get {
86 | return ResourceManager.GetString("CompressionFormatNoneText", resourceCulture);
87 | }
88 | }
89 |
90 | ///
91 | /// Looks up a localized string similar to RLE.
92 | ///
93 | internal static string CompressionFormatRLEText {
94 | get {
95 | return ResourceManager.GetString("CompressionFormatRLEText", resourceCulture);
96 | }
97 | }
98 |
99 | ///
100 | /// Looks up a localized string similar to Compression Format.
101 | ///
102 | internal static string CompressionFormatText {
103 | get {
104 | return ResourceManager.GetString("CompressionFormatText", resourceCulture);
105 | }
106 | }
107 |
108 | ///
109 | /// Looks up a localized string similar to File Version.
110 | ///
111 | internal static string FileVersionText {
112 | get {
113 | return ResourceManager.GetString("FileVersionText", resourceCulture);
114 | }
115 | }
116 |
117 | ///
118 | /// Looks up a localized string similar to The file contains an invalid block signature..
119 | ///
120 | internal static string InvalidBlockSignature {
121 | get {
122 | return ResourceManager.GetString("InvalidBlockSignature", resourceCulture);
123 | }
124 | }
125 |
126 | ///
127 | /// Looks up a localized string similar to The file width and height must be greater than zero..
128 | ///
129 | internal static string InvalidDocumentDimensions {
130 | get {
131 | return ResourceManager.GetString("InvalidDocumentDimensions", resourceCulture);
132 | }
133 | }
134 |
135 | ///
136 | /// Looks up a localized string similar to The file is not a valid Paint Shop Pro image..
137 | ///
138 | internal static string InvalidPSPFile {
139 | get {
140 | return ResourceManager.GetString("InvalidPSPFile", resourceCulture);
141 | }
142 | }
143 |
144 | ///
145 | /// Looks up a localized string similar to The selected Paint Shop Pro format supports a maximum of {0} layers..
146 | ///
147 | internal static string MaxLayersFormat {
148 | get {
149 | return ResourceManager.GetString("MaxLayersFormat", resourceCulture);
150 | }
151 | }
152 |
153 | ///
154 | /// Looks up a localized string similar to The file does not contain any Raster layers..
155 | ///
156 | internal static string RasterLayerNotFound {
157 | get {
158 | return ResourceManager.GetString("RasterLayerNotFound", resourceCulture);
159 | }
160 | }
161 |
162 | ///
163 | /// Looks up a localized string similar to {0} is not an expected block type, expecting {1} ({2}). .
164 | ///
165 | internal static string UnexpectedBlockTypeFormat {
166 | get {
167 | return ResourceManager.GetString("UnexpectedBlockTypeFormat", resourceCulture);
168 | }
169 | }
170 |
171 | ///
172 | /// Looks up a localized string similar to {0}-bit is not a supported image depth..
173 | ///
174 | internal static string UnsupportedBitDepth {
175 | get {
176 | return ResourceManager.GetString("UnsupportedBitDepth", resourceCulture);
177 | }
178 | }
179 |
180 | ///
181 | /// Looks up a localized string similar to The file uses a version of the Paint Shop Pro format that is not supported by this plugin..
182 | ///
183 | internal static string UnsupportedFormatVersion {
184 | get {
185 | return ResourceManager.GetString("UnsupportedFormatVersion", resourceCulture);
186 | }
187 | }
188 |
189 | ///
190 | /// Looks up a localized string similar to Paint Shop Pro 5.
191 | ///
192 | internal static string Version5 {
193 | get {
194 | return ResourceManager.GetString("Version5", resourceCulture);
195 | }
196 | }
197 |
198 | ///
199 | /// Looks up a localized string similar to Paint Shop Pro 6, 7, 8, 9.
200 | ///
201 | internal static string Version6AndLater {
202 | get {
203 | return ResourceManager.GetString("Version6AndLater", resourceCulture);
204 | }
205 | }
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/src/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | The file does not contain a color palette.
122 |
123 |
124 | LZ77
125 |
126 |
127 | None
128 |
129 |
130 | RLE
131 |
132 |
133 | Compression Format
134 |
135 |
136 | File Version
137 |
138 |
139 | The file contains an invalid block signature.
140 |
141 |
142 | The file width and height must be greater than zero.
143 |
144 |
145 | The file is not a valid Paint Shop Pro image.
146 |
147 |
148 | The selected Paint Shop Pro format supports a maximum of {0} layers.
149 |
150 |
151 | The file does not contain any Raster layers.
152 |
153 |
154 | {0} is not an expected block type, expecting {1} ({2}).
155 |
156 |
157 | {0}-bit is not a supported image depth.
158 |
159 |
160 | The file uses a version of the Paint Shop Pro format that is not supported by this plugin.
161 |
162 |
163 | Paint Shop Pro 5
164 |
165 |
166 | Paint Shop Pro 6, 7, 8, 9
167 |
168 |
--------------------------------------------------------------------------------