diff --git a/.gitattributes b/.gitattributes index 094765b3..4c251bbb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,20 +1,11 @@ # Exclude files from deployment: -## git files .gitattributes export-ignore .gitignore export-ignore -## duplicate documentation -PAGE.md export-ignore -README.md export-ignore -## build script -build_zip.sh export-ignore -## tools -tools export-ignore -## screenshots -/screenshots export-ignore -# Adjust GitHub linguist settings: -image_occlusion_enhanced/svg-edit/* linguist-vendored -image_occlusion_enhanced/Imaging/* linguist-vendored -image_occlusion_enhanced/uuid/* linguist-vendored -image_occlusion_enhanced/imagesize/* linguist-vendored -tools/fixiocards/* linguist-vendored -PAGE.md linguist-documentation \ No newline at end of file +.github export-ignore +docs export-ignore +screenshots export-ignore +addon.json export-ignore +# Adjust GitHub linguist settings +## Vendored +src/*/libaddon linguist-vendored +src/*/_vendor linguist-vendored diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..6deffc7d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ + +patreon: glutanimate +ko_fi: glutanimate diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..c3ad09f0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,42 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: 'bug' +assignees: '' + +--- + +#### Problem description + +*Please describe the issue concisely in here. In case of an error: Walk us through the steps you took to get there. What happened? What did you expect to happen?* + + +#### Checklist + +*Please replace the space inside the brackets with an **x** if the following items apply:* + + - [ ] I've restarted Anki to see if it helps + - [ ] I've verified that I use the latest version of the add-on by redownloading it from AnkiWeb + - [ ] I've verified that I use the latest version of Anki by checking at https://apps.ankiweb.net#download + - [ ] I've tried to disable other add-ons to see if there are any interactions present + - [ ] My issue disappears when I hold shift while starting Anki. + - [ ] I've checked if anyone else reported this problem before by looking through the issue reports. I also checked to see if there is a section about known issues in the add-on description, documentation, or README. + + +#### Information about your Anki set-up + +*Please open Anki, go to Help → About, click on "Copy Debug Info", and paste the result between the backticks below (if the button does not appear you are using an older version of Anki 2.1 and will need to update first):* + +``` + +``` + +#### Error message (if any) + +*If you've received an error message, please copy and paste it between the backticks below:* + + +```python + +``` \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..612d776d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,35 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: 'enhancement' +assignees: '' + +--- + +#### Checklist + +*Please replace the space inside the brackets with an **x** if the following items apply:* + + - [ ] I've verified that I use the latest version of the add-on by redownloading it from AnkiWeb + - [ ] I've verified that I use the latest version of Anki by checking at https://apps.ankiweb.net#download + - [ ] I've checked if anyone else suggested this feature before by looking through the issue reports. + +#### Problem case + +*Is your feature request related to a problem? If so, please describe it here. E.g.: "My workflow is such and such, and this and that would help."* + + + +#### Solution + +*Concisely describe the solution you would like* + + +*Concisely describe alternatives you've considered (if any)* + + + +#### More information + +*Additional context: Add any other context or screenshots about the feature request here.* diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 00000000..cc64cd02 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,21 @@ +--- +name: Question +about: Ask a question about this project +title: '' +labels: 'question' +assignees: '' + +--- + +#### Checklist + +*Please replace the space inside the brackets with an **x** if the following items apply:* + + - [ ] I've verified that I use the latest version of the add-on by redownloading it from AnkiWeb + - [ ] I've verified that I use the latest version of Anki by checking at https://apps.ankiweb.net#download + - [ ] I've checked if anyone else asked this question before by looking through the issue reports. + + +#### Your question + +*A clear and concise question about the add-on.* diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..14a59511 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,21 @@ +#### Description + +*Concisely describe what the pull request is trying to achieve. If pertinent, link to an existing issue report, or briefly explain the problem the PR is meant to solve. Feel free to attach screenshots or other media for UI-related changes.* + + +#### Checklist: + +*Please replace the space inside the brackets with an **x** and fill out the ellipses if the following items apply:* + +- [ ] I've read and understood the [contribution guidelines](./CONTRIBUTING.md) +- [ ] I've tested my changes against at least one of the following [Anki builds](https://apps.ankiweb.net/#download): + - [ ] Latest standard Anki 2.1 binary build + - [ ] Latest alternative Anki 2.1 binary build +- [ ] I've tested my changes on at least one of the following platforms: + - [ ] Linux, version: + - [ ] Windows, version: + - [ ] macOS, version: +- [ ] My changes potentially affect non-desktop platforms, of which I've tested: + - [ ] AnkiMobile, version: + - [ ] AnkiDroid, version: + - [ ] AnkiWeb diff --git a/.gitignore b/.gitignore index af35896d..bff9a7b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,10 @@ +# Linux FM +.hidden +.directory # Byte-compiled / optimized / DLL files *.pyo *.pyc __pycache__/ -# Anki -src/*/meta.json -# Build files -build -src/*/forms* -*-anki2*.zip # Python .python-version .mypy_cache/ @@ -19,29 +16,35 @@ venv/ ENV/ env.bak/ venv.bak/ +# Javascript +node_modules # IDEs *.sublime-project *.sublime-workspace .sublime-backup/ .idea/ .vscode/ +# Build files +build +src/*/gui/forms/anki* +src/*/gui/forms/LICENSE* +src/*/gui/resources/anki* +src/*/gui/resources/LICENSE* +*-anki2*.zip +# Dev tools +.bumpversion.cfg +labels.toml # Temp .gitold obsolete research -# Linux FM -.hidden -.directory -# Docs and tools -.github -docs/specs.md -docs/todo.md -docs/*.todo -docs/description.html -tools/test.py -# Add-on specific -docs/contribs.ui -image_occlusion_enhanced/svg-edit/build -image_occlusion_enhanced/svg-edit/Makefile -designer/ -image-occlusion-enhanced-*.zip +# Docs +todo +# Assets +resources/icons/optional +# Anki +src/*/meta.json +src/*/manifest.json +# Repo +src/image_occlusion_enhanced/svg-edit/svg-edit-master/ +.pytest_cache/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..87949982 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,491 @@ +# Changelog + +All notable changes to Image Occlusion Enhanced will be documented here. You can click on each release number to be directed to a detailed log of all code commits for that particular release. The download links will direct you to the GitHub release page, allowing you to manually install a release if you want. + +If you enjoy Image Occlusion Enhanced, please consider supporting my work on Patreon, or by buying me a cup of coffee :coffee:: + +
+ +:heart: My heartfelt thanks goes out to everyone who has supported this add-on through their tips, contributions, or any other means (you know who you are!). All of this would not have been possible without you. Thank you for being awesome! + +## [Unreleased] + +## [1.3.0-alpha6] - 2020-04-28 + +### [Download](https://github.com/glutanimate/image-occlusion-enhanced/releases/tag/v1.3.0-alpha6) + +### Fixed + +- Fixed note editing support on Anki 2.1.24+ (thanks to @zjosua for the fix!) +- Fixed a rare error that would occur when the add-on would update its template (thanks to Emma for the report!) + +## [1.3.0-alpha5] - 2019-09-11 + +### [Download](https://github.com/glutanimate/image-occlusion-enhanced/releases/tag/v1.3.0-alpha5) + +### Fixed + +- Handle malformatted and unrecognized image formats more graciously (thanks to Audrey for the report) + +## [1.3.0-alpha4] - 2019-04-14 + +### Added + +- Introduces .ankiaddon packaging format for GitHub builds + +### Fixed + +- Fixes error message when deleting current note in editing session (#87, thanks to @zjosua) +- Fixes rare error message when closing card browser + +## [1.3.0-alpha3] - 2018-12-24 + +### Fixed + +- Another uuid-related fix + +## [1.3.0-alpha2] - 2018-12-11 + +### Fixed + +- Quick hotfix for Anki 2.1.6 compatibility (now packages the UUID module which no longer seems to ship with Anki 2.1.6's Python distribution) + +## [1.3.0-alpha2] - 2018-12-11 + +### Fixed + +- Quick hotfix for Anki 2.1.6 compatibility (now packages the UUID module which no longer seems to ship with Anki 2.1.6's Python distribution) + +## [1.3.0-alpha1] - 2018-06-29 + +### Notes + +This marks the first official release of Image Occlusion Enhanced for Anki 2.1! Porting the add-on to Anki 2.1 has been [an arduous journey](https://github.com/glutanimate/image-occlusion-enhanced/projects/1), but I'm happy we're finally here. + +Before we dive into the actual changelog I would just like to thank each and everyone of you who has helped in this effort, either by [contributing to the codebase](https://github.com/glutanimate/image-occlusion-enhanced/graphs/contributors), filing [bug reports](https://github.com/glutanimate/image-occlusion-enhanced/issues), or by supporting my work directly through [tips](https://glutanimate.com/tip-jar/), [Patreon](https://www.patreon.com/glutanimate), or add-on commissions! + +In particular I would like to thank the following awesome people who support me on Patreon / have supported me at some point during the development of Image Occlusion Enhanced for Anki 2.1: + +- Blacky 372 +- Sebastián Ortega +- Edan Maor +- Peter Benisch + +**Important**: This is an Anki 2.1-only release (for now) + +### Added + +The primary focus in this release was Anki 2.1 compatibility, but v1.3.0 also comes with a number of nifty **new features** which I would like to highlight first: + +- You can now **occlude images in any note type you want**, either by **right clicking** on them and selecting the respective option, or by using the Image Occlusion button! +- As an added bonus: The new context menu introduced by the add-on will also allow you to **open any image with your default system viewer** – a great way to perform quick image editing tasks when needed. +- The masks editor now allows you to **add hints to your occlusion shapes**. In order to do so, simply create a text element on top of a shape and group it with the shape. +- You can now set a **custom hotkey** for invoking I/O. Gone are the days of conflicts with different keyboard locales! +- In-app **help screens** now guide users through the basic use of the add-on (including how to add cards, edit them, group masks, label items, etc.) + +(Some of the changes above will likely also be part of a future release of v1.3.0 for Anki 2.0.) + +### Fixed + +v1.3.0 for Anki 2.1 also comes with a plethora of **bug fixes** (some of the bugs fixed in this update have plagued I/O ever since its original release!): + +- Fix: Automatically remove accidentally drawn shapes. This addresses instances where users would end up with more cards than they should have because of invisible shapes drawn by the oversensitive editor component (especially with touch interfaces) +- Fix: Resolve issues with unicode characters in Anki path and/or image path. This should fix most of the problems users were experiencing with non-latin locales (e.g. the I/O editor screen remaining blank because SVG-Edit did not load, or various UnicodeError messages) +- Fix: More robust I/O editor instantiation. Should help address some of the stability issues users experienced over longer card creation sessions (e.g. needing to restart Anki to get I/O working again). +- and a large number of other smaller bug fixes and improvements + +### Changes + +There also some **changes to the workflow** in I/O v1.3.0 that you need to be aware of: + +- The default hotkey for invoking I/O is now Ctrl+Shift+O (customizable through the new settings entry) +- The card generation options have been renamed and simplified: You can now choose between "Hide All, Guess One" (used to be "Hide All, Reveal One") and "Hide One, Guess One" (used to be "Hide All, Reveal All"). My hope with these new names is that they will be more intuitive for new users. (thanks a lot to Tiago Barroso for the suggestion!) +- "Hide All, Reveal All" is no longer available as a mask generation option. With the mask reveal button introduced in recent I/O releases it no longer served much of a purpose and was mostly confusing new users as they expected it to work like a grouped occlusion of all shapes. + + Just in case you were using this option and are now wondering how to cover the same use cases: + + + In case you were using "Hide All, Reveal All" to uncover all labels on the back: Try to switch to using "Hide One, Guess One" coupled with the mask reveal button on the backside (hotkey: `G`) + + In case you were using "Hide All, Reveal All" to 'group' your shapes: Use the [actual grouping feature](https://github.com/glutanimate/image-occlusion-enhanced/wiki/Advanced-Use#grouping-items) instead + +### Limitations + +There are a number of known limitations to this alpha release that you need to be aware of: + +- Due to compatibility issues between SVG-Edit and the new Chromium renderer in Anki 2.1 some of the features in the masks editor no longer work correctly. My hope is to address these in the following beta release: + + [Pointer not changing to selection mode when clicking on shape](https://github.com/glutanimate/image-occlusion-enhanced/issues/54) + + [Path tool no longer working](https://github.com/glutanimate/image-occlusion-enhanced/issues/56) + + [https://github.com/glutanimate/image-occlusion-enhanced/issues/57](https://github.com/glutanimate/image-occlusion-enhanced/issues/57) +- Please do not invoke the add-on's settings menu while the I/O Editor is running. There is currently no support for updating I/O editing sessions at runtime, and while most settings will simply only not be applied, others might cause the add-on to stop working correctly until the editor session is restarted. The same applies to modifications to the add-on's note type via Anki's built-in note type manager. + +Of course there also bound to be some unforeseen bugs and regressions in the alpha. If you experience any of these please make sure to either report them on the add-on's [bug tracker](https://github.com/glutanimate/image-occlusion-enhanced/issues) or in the [official support thread](https://anki.tenderapp.com/discussions/add-ons/8295-image-occlusion-enhanced-official-thread). + +## [1.2.2] - 2017-04-04 + +### Fixed + +- Fixed: rare AttributeError when changing the image +- Fixed: GIFs should be supported on Windows and macOS now +- Fixed: incompatibility with upcoming release of "Quick not and deck buttons" add-on + +## [1.2.1] - 2017-02-14 + +### Fixed + +- Fixed: Unicode TypeError on Windows + + +## [1.2.0] - 2017-02-14 + +### Added + +- **New**: Hotkey for toggling masks on the answer side (`G`) +- **New**: Preserve scrollbar position when revealing the answer +- **New**: Reuse existing images when creating multiple occlusion sets from the same base image +- **New**: Limit image display height in editor fields in order to improve navigating notes in the card browser + +### Changed + +- Changed: Updated default field order to move the question mask below the original image. Should make it easier to identify each respective card in the browser. + +### Fixed + +- Fixed: Remove card margins on mobile devices +- Fixed: Increased size of mask toggle button on mobile devices + +### Notes + +Please note that the changes to the field order and card templates only apply to new installations of the add-on. I've decided against enforcing these changes on existing installations as that would undo any customizations you might have applied to the note type. If you'd like to update your cards with these changes, please follow [the instructions in the Wiki](https://github.com/Glutanimate/image-occlusion-enhanced/wiki/Troubleshooting#resetting-note-type-and-template-to-the-defaults) to reset your field order and card template to the (new) defaults. + + +## [1.1.1] - 2017-01-20 + +### Fixed + +- Clicking on a context menu entry would launch the web browser under some circumstances (thanks to PolymorphicVTach for the report) + + +## [1.1.0] - 2017-01-14 + +### Added + +- **New**: Grid-snapping for shapes can now be toggled via a button in the upper panel (hotkey: _Shift+S_) +- **New**: Panning tool (hotkey: _Q_) +- **New**: Control zoom levels with _+/-_ +- **New**: Reset zoom with _0_ + +### Fixed + +- Fixed: Error when marking the Image field as sticky (thanks to vidale3 for the report) +- Fixed: _Ctrl+Shift+T_ now focuses the tag field again + +### Changed + +- Improved: Images now use the available canvas space slightly more efficiently +- plus a number of smaller improvements and bug fixes + + +## [1.0.4] - 2016-12-14 + +### Added + +- add hotkeys for switching between layers (Ctrl+Shift+L and Ctrl+Shift+M) + +### Fixed + +- fix an encoding issue when editing labels + +## [1.0.3] - 2016-12-01 + +### Fixed + +- fix a runtime error that was occuring for some Windows users + + +## [1.0.2] - 2016-11-24 + +### Fixed + +- Fix unicode support in labels + + +## [1.0.1] - 2016-11-16 + +### Fixed + +- Restore proper window controls on Windows + + +## [1.0.0] - 2016-11-09 + +The most comprehensive update to _Image Occlusion Enhanced_ since its inception: + +### Added + +- **Modify Existing Notes** + - Need to remove or add a shape, update a field, or resize all masks? Now you can! +- **Change Images on the Fly** + - Simply switch to a different image to occlude right from the IO Editor +- **New Occlusion Mode** + - Hides all labels on the question side, and reveals all of them on the back + - The different occlusion modes now also follow a new naming scheme. It should be self-explanatory what each of them does, but you can hover over the respective button to get a more detailed description +- **Completely Reworked Note Type** + - 4 additional fields to give your notes enough space for all the extra information they might need + - New intuitive field order, with the Header and Image right on top. No more issues identifying your notes in the Browser! +- **Full Customization** + - You can now add as many fields to the note type as you like + - New Options entry for renaming default fields + +### Changed + +- **Updated Options Interface** + - More options, fewer bugs +- **Fully Rewritten Note Generator** + - Faster, more extensible, and less bug-prone +- **Performance Improvements** + - Smaller memory footprint in general use (by about 30MB) + +### Fixed + +- **Stability Improvements** + - Bug fixes everywhere + + +## [1.0.0-beta6] - 2016-11-07 + +### Added + +- **New**: Use a button instead of clicking the image to reveal all masks. The old method interfered with gesture support on mobile clients. +- **New**: Default action hotkey (Ctrl+Return) + +### Changed + +- **Other**: Set Extra fields to be note-specific by default + +## [1.0.0-beta5] - 2016-11-05 + +### Added + +- **New**: Reveal all occluded areas when clicking image on answer side – This is a somewhat of an experimental change as there's no official support for JavaScript in Anki. Note: Normally this would require an update to the IO note type, but given that this is an experimental change I decided not to force the update. Instead, feel free to test this new feature on an empty Anki profile. + +### Fixed + +- **Fix**: Force media sync when updating mask files +- **Fix**: Handle deleted IO note type more graciously + +### Changed + +- **Other**: Updated tooltips for occlusion types (@dgbeecher) + + +## [1.0.0-beta4] - 2016-10-18 + +### Changed + +- Updated the default card template + - The new template includes the two new extra fields and uses a more elaborate layout for all sections below the image. It also provides much needed adjustments of the styling for AnkiMobile and AnkiDroid + - **Important**: This will overwrite any previous changes to the card templates you might have performed. If you've used a previous Beta and customized your template or styles please make sure to back them up before installing this version. + - FWIW, drastic changes like this will only happen with Beta releases. If I ever see the need to change the template for a stable update I will implement it in a way that asks you for confirmation first. + +### Fixed + +- Fixed a number of smaller issues (thanks to @dgbeecher for reporting these!) + + +## [1.0.0-beta3] - 2016-10-15 + +### Added + +- New options for labels and lines +- New option for ignoring fields when editing +- New error dialog that provides a help button + +### Changed + +- More verbose tooltips when generating notes + +### Fixed + +- Line color and width should now be preserved when switching to a different tool +- Lots of smaller bug fixes + + +## [1.0.0-beta2] - 2016-10-13 + +### Fixed + +- Possible fix for a module import error on macOS + +## [1.0.0-beta1] - 2016-10-12 + +A tremendous version jump, I know, but this is the most comprehensive update to Image Occlusion since the release of _Image Occlusion Enhanced_ + +### Added + +- **Modify Existing Notes** + - Need to remove or add a shape, update a field, or resize all masks? Now you can! +- **Change Images on the Fly** + - Simply switch to a different image to occlude right from the IO Editor +- **New Occlusion Mode** + - Hides all labels on the question side, and reveals all of them on the back + - The different occlusion modes now also follow a new naming scheme. It should be self-explanatory what each of them does, but you can hover over the respective button to get a more detailed description +- **Completely Reworked Note Type** + - 4 additional fields to give your notes enough space for all the extra information they might need + - New intuitive field order, with the Header and Image right on top. No more issues identifying your notes in the Browser! +- **Full Customization** + - You can now add as many fields to the note type as you like + - New Options entry for renaming default fields + +### Changed + +- **Updated Options Interface** + - More options, fewer bugs +- **Fully Rewritten Note Generator** + - Faster, more extensible, and less bug-prone +- **Performance Improvements** + - Smaller memory footprint in general use (by about 30MB) + +### Fixed + +- **Stability Improvements** + - Bug fixes everywhere + +## [0.3.0] - 2016-09-28 + +### Changed + +- SVGEdit: fixed most of the random opacity changes +- SVGEdit: fixed some issues with the stroke and fill attributes +- SVGEdit: changed default text font +- SVGEdit: added initial fill color to the color palette +- SVGEdit: updated hotkey assignments to improve usability +- SVGEdit: added "Esc" hotkey to deselect current selection + + +## [0.2.6] - 2016-09-22 + +### Changed + +- improved IO note type sanity checks + +## [0.2.5] - 2016-09-12 + +### Changed + +- New add-on name +- Added link to new Wiki + +## [0.2.4] - 2016-08-25 + +### Fixed + +- Several bug fixes and improvements + +## [0.2.3] - 2016-05-19 + +### Changed + +- Update mask fill colour when upgrading from Image Occlusion 2.0 + + +## [0.2.2] - 2016-04-16 + +### Added + +- added support for preserving occlusions and labels when creating new notes based on old one + + +## [0.2.1] - 2016-04-04 + +### Fixed + +- fixed an encoding error on Windows + + +## [0.2.0] - 2016-04-03 + +### Changed + +- first release on AnkiWeb +- fixes a number of issues with SVG-Edit + + +## [0.1.4] - 2016-04-01 + +### Changed + +- improvements to the Masks editor +- bug fix: support for special characters in file names on Windows + + +## [0.1.3] - 2016-04-01 + +### Changed + +- update the Options window and Help link +- remember window geometry across sessions +- allow upgrading directly from I/O 2.0 +- a few miscellaneous fixes + + +## [0.1.2] - 2016-03-29 + +### Fixed + +- More fixes. + + +## [0.1.1] - 2016-03-28 + +### Fixed + +- A few smaller fixes. + + +## v0.1.0 - 2016-03-28 + +### Added + +- First release of Image Occlusion 2.0 Enhanced. +- Still needs a lot of testing! + + +[Unreleased]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.3.0-alpha5...HEAD +[1.3.0-alpha5]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.3.0-alpha4...v1.3.0-alpha5 +[1.3.0-alpha4]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.3.0-alpha3...v1.3.0-alpha4 +[1.3.0-alpha3]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.3.0-alpha2...v1.3.0-alpha3 +[1.3.0-alpha2]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.3.0-alpha1...v1.3.0-alpha2 +[1.3.0-alpha1]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.2.2...v1.3.0-alpha1 +[1.2.2]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.2.1...v1.2.2 +[1.2.1]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.2.0...v1.2.1 +[1.2.0]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.1.1...v1.2.0 +[1.1.1]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.1.0...v1.1.1 +[1.1.0]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.0.4...v1.1.0 +[1.0.4]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.0.3...v1.0.4 +[1.0.3]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.0.2...v1.0.3 +[1.0.2]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.0.1...v1.0.2 +[1.0.1]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.0.0...v1.0.1 +[1.0.0]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.0.0-beta6...v1.0.0 +[1.0.0-beta6]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.0.0-beta5...v1.0.0-beta6 +[1.0.0-beta5]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.0.0-beta4...v1.0.0-beta5 +[1.0.0-beta4]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.0.0-beta3...v1.0.0-beta4 +[1.0.0-beta3]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.0.0-beta2...v1.0.0-beta3 +[1.0.0-beta2]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v1.0.0-beta1...v1.0.0-beta2 +[1.0.0-beta1]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v0.3.0...v1.0.0-beta1 +[0.3.0]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v0.2.6...v0.3.0 +[0.2.6]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v0.2.5...v0.2.6 +[0.2.5]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v0.2.4...v0.2.5 +[0.2.4]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v0.2.3...v0.2.4 +[0.2.3]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v0.2.2...v0.2.3 +[0.2.2]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v0.2.1...v0.2.2 +[0.2.1]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v0.2.0...v0.2.1 +[0.2.0]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v0.1.4...v0.2.0 +[0.1.4]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v0.1.3...v0.1.4 +[0.1.3]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v0.1.2...v0.1.3 +[0.1.2]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v0.1.1...v0.1.2 +[0.1.1]: https://github.com/Glutanimate/image-occlusion-enhanced/compare/v0.1.0...v0.1.1 + +----- + +The format of this file is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..cac2bd92 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,5 @@ +## How to Contribute to this Project + +Please see the [common contribution guidelines](https://github.com/glutanimate/docs/blob/master/anki/add-ons/CONTRIBUTING.md#how-to-contribute-to-my-anki-add-ons) for my Anki add-ons. + +Thanks! \ No newline at end of file diff --git a/Image Occlusion Enhanced.py b/Image Occlusion Enhanced.py deleted file mode 100644 index 46ddfa8d..00000000 --- a/Image Occlusion Enhanced.py +++ /dev/null @@ -1,4 +0,0 @@ -# This file imports Image Occlusion Enhanced into Anki -# Please don't edit this if you don't know what you're doing. - -import image_occlusion_enhanced.main diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..8f2a235d --- /dev/null +++ b/LICENSE @@ -0,0 +1,699 @@ +This Program is licensed under the GNU Affero General Public License +version 3 ("AGPL"), extended by a number of Additional Terms under +Section 7 of the AGPL. + +If not otherwise noted, this License applies to all files included +with this Program. Files subject to different licensing terms might +also ship with this Program (e.g. the libaddon package), but will +be clearly marked as such in additional LICENSE files +accompanying them. + +The AGPLv3 License and Additional Terms follow. + +============================================================================== + + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc.
-### Changes Compared to Image Occlusion 2.0
+### Installation
-- **Modify Existing Notes**: Update and modify your IO notes to your heart's content
-- **Change Images on the Fly**: Switch to a different image right from the Editor
-- **Create Custom Labels**: Your image is lacking a specific label? Now you can create it yourself!
-- **Completely Overhauled UI**: Tabs, multi-line entry fields, numerous new hotkey assignments
-- **Completely Reworked Note Type**: 4 additional fields, new intuitive field order
-- **Updated Card Template**: New card layout, a button to reveal all masks, optimizations for AnkiMobile
-- **Full Customization**: Add and rename as many fields as you like
-- **Updated Options Interface**: More options, fewer bugs
-- **Fully Rewritten Note Generator**: Faster, more extensible, and less bug-prone
-- **Performance Improvements**: Smaller memory footprint in general use
-- **Stability Improvements**: Bug fixes everywhere
+#### AnkiWeb
-For more information on recent improvements and additions make sure to check out the changelogs posted on the [Releases page](https://github.com/Glutanimate/image-occlusion-enhanced/releases).
+The easiest way to install Image Occlusion Enhanced is through [AnkiWeb](https://ankiweb.net/shared/info/1374772155).
+
+#### Manual installation
+
+1. Make sure you have the [latest version](https://apps.ankiweb.net/#download) of Anki 2.1 installed. Earlier releases (e.g. found in various Linux distros) do not support `.ankiaddon` packages.
+2. Download the latest `.ankiaddon` package from the [releases tab](https://github.com/glutanimate/image-occlusion-enhanced/releases) (you might need to click on *Assets* below the description to reveal the download links)
+3. From Anki's main window, head to *Tools* → *Add-ons*
+4. Drag-and-drop the `.ankiaddon` package onto the add-ons list
+5. Restart Anki
### Documentation
The installation and use of this add-on is detailed in the [Wiki](https://github.com/Glutanimate/image-occlusion-enhanced/wiki) and a [series of video tutorials on YouTube](https://www.youtube.com/playlist?list=PL3MozITKTz5YFHDGB19ypxcYfJ1ITk_6o). More information may also be found in the [AnkiWeb description](docs/description.md).
-### Known Issues
+### Building
+
+With [Anki add-on builder](https://github.com/glutanimate/anki-addon-builder/) installed:
-*Image Occlusion Enhanced* should now be stable for the most part. However, there still exist a number of longstanding issues that have less to do with the add-on itself and more with Anki and the libraries it's based on. macOS, in particular, has always suffered from compatibility issues with SVG-Edit.
+ git clone https://github.com/glutanimate/image-occlusion-enhanced.git
+ cd image-occlusion-enhanced
+ aab build
-For a list of known issues please check out the [Issues](https://github.com/Glutanimate/image-occlusion-enhanced/issues) page. Bug reports and suggestions are always welcome, but it might take me a while to get to them. If you know how to code please feel free to improve this project, file pull requests, etc.
+For more information on the build process please refer to [`aab`'s documentation](https://github.com/glutanimate/anki-addon-builder/#usage).
-### Credits and License
+### Contributing
+
+Contributions are welcome! Please review the [contribution guidelines](./CONTRIBUTING.md) on how to:
+
+- Report issues
+- File pull requests
+- Support the project as a non-developer
+
+### License and Credits
+
+*Image Occlusion Enhanced* is
*Copyright © 2012-2015 [Tiago Barroso](https://github.com/tmbb)*
*Copyright © 2013 [Steve AW](https://github.com/steveaw)*
-*Copyright © 2016-2017 [Aristotelis P.](https://glutanimate.com/)*
+*Copyright © 2016-2019 [Aristotelis P.](https://glutanimate.com/) (Glutanimate)*
+
+With code contributions from: Damien Elmes, Kyle Mills, James Kraus, Matt Restko
-----
@@ -73,6 +102,12 @@ I would also like to extend my heartfelt thanks to everyone who has helped with
- [imagesize.py](https://github.com/shibukawa/imagesize_py) v0.7.1. Copyright (c) 2016 Yoshiki Shibukawa. Licensed under the MIT license.
-This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+Image Occlusion Enhanced is free and open-source software. The add-on code that runs within Anki is released under the GNU AGPLv3 license, extended by a number of additional terms. For more information please see the [LICENSE](https://github.com/glutanimate/image-occlusion-enhanced/blob/master/LICENSE) file that accompanied this program.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY.
-This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
\ No newline at end of file
+----
+
+- # This method is similar to the {@link #fromstring} function, but - # loads data into this image instead of creating a new image - # object. - - def fromstring(self, data, decoder_name="raw", *args): - "Load data to image from binary string" - - # may pass tuple instead of argument list - if len(args) == 1 and isTupleType(args[0]): - args = args[0] - - # default format - if decoder_name == "raw" and args == (): - args = self.mode - - # unpack data - d = _getdecoder(self.mode, decoder_name, args) - d.setimage(self.im) - s = d.decode(data) - - if s[0] >= 0: - raise ValueError("not enough image data") - if s[1] != 0: - raise ValueError("cannot decode image data") - - ## - # Allocates storage for the image and loads the pixel data. In - # normal cases, you don't need to call this method, since the - # Image class automatically loads an opened image when it is - # accessed for the first time. - # - # @return An image access object. - - def load(self): - "Explicitly load pixel data." - if self.im and self.palette and self.palette.dirty: - # realize palette - apply(self.im.putpalette, self.palette.getdata()) - self.palette.dirty = 0 - self.palette.mode = "RGB" - self.palette.rawmode = None - if self.info.has_key("transparency"): - self.im.putpalettealpha(self.info["transparency"], 0) - self.palette.mode = "RGBA" - if self.im: - return self.im.pixel_access(self.readonly) - - ## - # Verifies the contents of a file. For data read from a file, this - # method attempts to determine if the file is broken, without - # actually decoding the image data. If this method finds any - # problems, it raises suitable exceptions. If you need to load - # the image after using this method, you must reopen the image - # file. - - def verify(self): - "Verify file contents." - pass - - ## - # Returns a converted copy of this image. For the "P" mode, this - # method translates pixels through the palette. If mode is - # omitted, a mode is chosen so that all information in the image - # and the palette can be represented without a palette. - #
- # The current version supports all possible conversions between - # "L", "RGB" and "CMYK." - #
- # When translating a colour image to black and white (mode "L"), - # the library uses the ITU-R 601-2 luma transform: - #
- # L = R * 299/1000 + G * 587/1000 + B * 114/1000 - #
- # When translating a greyscale image into a bilevel image (mode - # "1"), all non-zero values are set to 255 (white). To use other - # thresholds, use the {@link #Image.point} method. - # - # @def convert(mode, matrix=None, **options) - # @param mode The requested mode. - # @param matrix An optional conversion matrix. If given, this - # should be 4- or 16-tuple containing floating point values. - # @param options Additional options, given as keyword arguments. - # @keyparam dither Dithering method, used when converting from - # mode "RGB" to "P". - # Available methods are NONE or FLOYDSTEINBERG (default). - # @keyparam palette Palette to use when converting from mode "RGB" - # to "P". Available palettes are WEB or ADAPTIVE. - # @keyparam colors Number of colors to use for the ADAPTIVE palette. - # Defaults to 256. - # @return An Image object. - - def convert(self, mode=None, data=None, dither=None, - palette=WEB, colors=256): - "Convert to other pixel format" - - if not mode: - # determine default mode - if self.mode == "P": - self.load() - if self.palette: - mode = self.palette.mode - else: - mode = "RGB" - else: - return self.copy() - - self.load() - - if data: - # matrix conversion - if mode not in ("L", "RGB"): - raise ValueError("illegal conversion") - im = self.im.convert_matrix(mode, data) - return self._new(im) - - if mode == "P" and palette == ADAPTIVE: - im = self.im.quantize(colors) - return self._new(im) - - # colourspace conversion - if dither is None: - dither = FLOYDSTEINBERG - - try: - im = self.im.convert(mode, dither) - except ValueError: - try: - # normalize source image and try again - im = self.im.convert(getmodebase(self.mode)) - im = im.convert(mode, dither) - except KeyError: - raise ValueError("illegal conversion") - - return self._new(im) - - def quantize(self, colors=256, method=0, kmeans=0, palette=None): - - # methods: - # 0 = median cut - # 1 = maximum coverage - - # NOTE: this functionality will be moved to the extended - # quantizer interface in a later version of PIL. - - self.load() - - if palette: - # use palette from reference image - palette.load() - if palette.mode != "P": - raise ValueError("bad mode for palette image") - if self.mode != "RGB" and self.mode != "L": - raise ValueError( - "only RGB or L mode images can be quantized to a palette" - ) - im = self.im.convert("P", 1, palette.im) - return self._makeself(im) - - im = self.im.quantize(colors, method, kmeans) - return self._new(im) - - ## - # Copies this image. Use this method if you wish to paste things - # into an image, but still retain the original. - # - # @return An Image object. - - def copy(self): - "Copy raster data" - - self.load() - im = self.im.copy() - return self._new(im) - - ## - # Returns a rectangular region from this image. The box is a - # 4-tuple defining the left, upper, right, and lower pixel - # coordinate. - #
- # This is a lazy operation. Changes to the source image may or - # may not be reflected in the cropped image. To break the - # connection, call the {@link #Image.load} method on the cropped - # copy. - # - # @param The crop rectangle, as a (left, upper, right, lower)-tuple. - # @return An Image object. - - def crop(self, box=None): - "Crop region from image" - - self.load() - if box is None: - return self.copy() - - # lazy operation - return _ImageCrop(self, box) - - ## - # Configures the image file loader so it returns a version of the - # image that as closely as possible matches the given mode and - # size. For example, you can use this method to convert a colour - # JPEG to greyscale while loading it, or to extract a 128x192 - # version from a PCD file. - #
- # Note that this method modifies the Image object in place. If - # the image has already been loaded, this method has no effect. - # - # @param mode The requested mode. - # @param size The requested size. - - def draft(self, mode, size): - "Configure image decoder" - - pass - - def _expand(self, xmargin, ymargin=None): - if ymargin is None: - ymargin = xmargin - self.load() - return self._new(self.im.expand(xmargin, ymargin, 0)) - - ## - # Filters this image using the given filter. For a list of - # available filters, see the ImageFilter module. - # - # @param filter Filter kernel. - # @return An Image object. - # @see ImageFilter - - def filter(self, filter): - "Apply environment filter to image" - - self.load() - - if callable(filter): - filter = filter() - if not hasattr(filter, "filter"): - raise TypeError("filter argument should be ImageFilter.Filter instance or class") - - if self.im.bands == 1: - return self._new(filter.filter(self.im)) - # fix to handle multiband images since _imaging doesn't - ims = [] - for c in range(self.im.bands): - ims.append(self._new(filter.filter(self.im.getband(c)))) - return merge(self.mode, ims) - - ## - # Returns a tuple containing the name of each band in this image. - # For example, getbands on an RGB image returns ("R", "G", "B"). - # - # @return A tuple containing band names. - - def getbands(self): - "Get band names" - - return ImageMode.getmode(self.mode).bands - - ## - # Calculates the bounding box of the non-zero regions in the - # image. - # - # @return The bounding box is returned as a 4-tuple defining the - # left, upper, right, and lower pixel coordinate. If the image - # is completely empty, this method returns None. - - def getbbox(self): - "Get bounding box of actual data (non-zero pixels) in image" - - self.load() - return self.im.getbbox() - - ## - # Returns a list of colors used in this image. - # - # @param maxcolors Maximum number of colors. If this number is - # exceeded, this method returns None. The default limit is - # 256 colors. - # @return An unsorted list of (count, pixel) values. - - def getcolors(self, maxcolors=256): - "Get colors from image, up to given limit" - - self.load() - if self.mode in ("1", "L", "P"): - h = self.im.histogram() - out = [] - for i in range(256): - if h[i]: - out.append((h[i], i)) - if len(out) > maxcolors: - return None - return out - return self.im.getcolors(maxcolors) - - ## - # Returns the contents of this image as a sequence object - # containing pixel values. The sequence object is flattened, so - # that values for line one follow directly after the values of - # line zero, and so on. - #
- # Note that the sequence object returned by this method is an - # internal PIL data type, which only supports certain sequence - # operations. To convert it to an ordinary sequence (e.g. for - # printing), use list(im.getdata()). - # - # @param band What band to return. The default is to return - # all bands. To return a single band, pass in the index - # value (e.g. 0 to get the "R" band from an "RGB" image). - # @return A sequence-like object. - - def getdata(self, band = None): - "Get image data as sequence object." - - self.load() - if band is not None: - return self.im.getband(band) - return self.im # could be abused - - ## - # Gets the the minimum and maximum pixel values for each band in - # the image. - # - # @return For a single-band image, a 2-tuple containing the - # minimum and maximum pixel value. For a multi-band image, - # a tuple containing one 2-tuple for each band. - - def getextrema(self): - "Get min/max value" - - self.load() - if self.im.bands > 1: - extrema = [] - for i in range(self.im.bands): - extrema.append(self.im.getband(i).getextrema()) - return tuple(extrema) - return self.im.getextrema() - - ## - # Returns a PyCObject that points to the internal image memory. - # - # @return A PyCObject object. - - def getim(self): - "Get PyCObject pointer to internal image memory" - - self.load() - return self.im.ptr - - - ## - # Returns the image palette as a list. - # - # @return A list of color values [r, g, b, ...], or None if the - # image has no palette. - - def getpalette(self): - "Get palette contents." - - self.load() - try: - return map(ord, self.im.getpalette()) - except ValueError: - return None # no palette - - - ## - # Returns the pixel value at a given position. - # - # @param xy The coordinate, given as (x, y). - # @return The pixel value. If the image is a multi-layer image, - # this method returns a tuple. - - def getpixel(self, xy): - "Get pixel value" - - self.load() - return self.im.getpixel(xy) - - ## - # Returns the horizontal and vertical projection. - # - # @return Two sequences, indicating where there are non-zero - # pixels along the X-axis and the Y-axis, respectively. - - def getprojection(self): - "Get projection to x and y axes" - - self.load() - x, y = self.im.getprojection() - return map(ord, x), map(ord, y) - - ## - # Returns a histogram for the image. The histogram is returned as - # a list of pixel counts, one for each pixel value in the source - # image. If the image has more than one band, the histograms for - # all bands are concatenated (for example, the histogram for an - # "RGB" image contains 768 values). - #
- # A bilevel image (mode "1") is treated as a greyscale ("L") image - # by this method. - #
- # If a mask is provided, the method returns a histogram for those - # parts of the image where the mask image is non-zero. The mask - # image must have the same size as the image, and be either a - # bi-level image (mode "1") or a greyscale image ("L"). - # - # @def histogram(mask=None) - # @param mask An optional mask. - # @return A list containing pixel counts. - - def histogram(self, mask=None, extrema=None): - "Take histogram of image" - - self.load() - if mask: - mask.load() - return self.im.histogram((0, 0), mask.im) - if self.mode in ("I", "F"): - if extrema is None: - extrema = self.getextrema() - return self.im.histogram(extrema) - return self.im.histogram() - - ## - # (Deprecated) Returns a copy of the image where the data has been - # offset by the given distances. Data wraps around the edges. If - # yoffset is omitted, it is assumed to be equal to xoffset. - #
- # This method is deprecated. New code should use the offset - # function in the ImageChops module. - # - # @param xoffset The horizontal distance. - # @param yoffset The vertical distance. If omitted, both - # distances are set to the same value. - # @return An Image object. - - def offset(self, xoffset, yoffset=None): - "(deprecated) Offset image in horizontal and/or vertical direction" - if warnings: - warnings.warn( - "'offset' is deprecated; use 'ImageChops.offset' instead", - DeprecationWarning, stacklevel=2 - ) - import ImageChops - return ImageChops.offset(self, xoffset, yoffset) - - ## - # Pastes another image into this image. The box argument is either - # a 2-tuple giving the upper left corner, a 4-tuple defining the - # left, upper, right, and lower pixel coordinate, or None (same as - # (0, 0)). If a 4-tuple is given, the size of the pasted image - # must match the size of the region. - #
- # If the modes don't match, the pasted image is converted to the - # mode of this image (see the {@link #Image.convert} method for - # details). - #
- # Instead of an image, the source can be a integer or tuple - # containing pixel values. The method then fills the region - # with the given colour. When creating RGB images, you can - # also use colour strings as supported by the ImageColor module. - #
- # If a mask is given, this method updates only the regions - # indicated by the mask. You can use either "1", "L" or "RGBA" - # images (in the latter case, the alpha band is used as mask). - # Where the mask is 255, the given image is copied as is. Where - # the mask is 0, the current value is preserved. Intermediate - # values can be used for transparency effects. - #
- # Note that if you paste an "RGBA" image, the alpha band is - # ignored. You can work around this by using the same image as - # both source image and mask. - # - # @param im Source image or pixel value (integer or tuple). - # @param box An optional 4-tuple giving the region to paste into. - # If a 2-tuple is used instead, it's treated as the upper left - # corner. If omitted or None, the source is pasted into the - # upper left corner. - #
- # If an image is given as the second argument and there is no - # third, the box defaults to (0, 0), and the second argument - # is interpreted as a mask image. - # @param mask An optional mask image. - # @return An Image object. - - def paste(self, im, box=None, mask=None): - "Paste other image into region" - - if isImageType(box) and mask is None: - # abbreviated paste(im, mask) syntax - mask = box; box = None - - if box is None: - # cover all of self - box = (0, 0) + self.size - - if len(box) == 2: - # lower left corner given; get size from image or mask - if isImageType(im): - size = im.size - elif isImageType(mask): - size = mask.size - else: - # FIXME: use self.size here? - raise ValueError( - "cannot determine region size; use 4-item box" - ) - box = box + (box[0]+size[0], box[1]+size[1]) - - if isStringType(im): - import ImageColor - im = ImageColor.getcolor(im, self.mode) - - elif isImageType(im): - im.load() - if self.mode != im.mode: - if self.mode != "RGB" or im.mode not in ("RGBA", "RGBa"): - # should use an adapter for this! - im = im.convert(self.mode) - im = im.im - - self.load() - if self.readonly: - self._copy() - - if mask: - mask.load() - self.im.paste(im, box, mask.im) - else: - self.im.paste(im, box) - - ## - # Maps this image through a lookup table or function. - # - # @param lut A lookup table, containing 256 values per band in the - # image. A function can be used instead, it should take a single - # argument. The function is called once for each possible pixel - # value, and the resulting table is applied to all bands of the - # image. - # @param mode Output mode (default is same as input). In the - # current version, this can only be used if the source image - # has mode "L" or "P", and the output has mode "1". - # @return An Image object. - - def point(self, lut, mode=None): - "Map image through lookup table" - - self.load() - - if isinstance(lut, ImagePointHandler): - return lut.point(self) - - if not isSequenceType(lut): - # if it isn't a list, it should be a function - if self.mode in ("I", "I;16", "F"): - # check if the function can be used with point_transform - scale, offset = _getscaleoffset(lut) - return self._new(self.im.point_transform(scale, offset)) - # for other modes, convert the function to a table - lut = map(lut, range(256)) * self.im.bands - - if self.mode == "F": - # FIXME: _imaging returns a confusing error message for this case - raise ValueError("point operation not supported for this mode") - - return self._new(self.im.point(lut, mode)) - - ## - # Adds or replaces the alpha layer in this image. If the image - # does not have an alpha layer, it's converted to "LA" or "RGBA". - # The new layer must be either "L" or "1". - # - # @param im The new alpha layer. This can either be an "L" or "1" - # image having the same size as this image, or an integer or - # other color value. - - def putalpha(self, alpha): - "Set alpha layer" - - self.load() - if self.readonly: - self._copy() - - if self.mode not in ("LA", "RGBA"): - # attempt to promote self to a matching alpha mode - try: - mode = getmodebase(self.mode) + "A" - try: - self.im.setmode(mode) - except (AttributeError, ValueError): - # do things the hard way - im = self.im.convert(mode) - if im.mode not in ("LA", "RGBA"): - raise ValueError # sanity check - self.im = im - self.mode = self.im.mode - except (KeyError, ValueError): - raise ValueError("illegal image mode") - - if self.mode == "LA": - band = 1 - else: - band = 3 - - if isImageType(alpha): - # alpha layer - if alpha.mode not in ("1", "L"): - raise ValueError("illegal image mode") - alpha.load() - if alpha.mode == "1": - alpha = alpha.convert("L") - else: - # constant alpha - try: - self.im.fillband(band, alpha) - except (AttributeError, ValueError): - # do things the hard way - alpha = new("L", self.size, alpha) - else: - return - - self.im.putband(alpha.im, band) - - ## - # Copies pixel data to this image. This method copies data from a - # sequence object into the image, starting at the upper left - # corner (0, 0), and continuing until either the image or the - # sequence ends. The scale and offset values are used to adjust - # the sequence values: pixel = value*scale + offset. - # - # @param data A sequence object. - # @param scale An optional scale value. The default is 1.0. - # @param offset An optional offset value. The default is 0.0. - - def putdata(self, data, scale=1.0, offset=0.0): - "Put data from a sequence object into an image." - - self.load() - if self.readonly: - self._copy() - - self.im.putdata(data, scale, offset) - - ## - # Attaches a palette to this image. The image must be a "P" or - # "L" image, and the palette sequence must contain 768 integer - # values, where each group of three values represent the red, - # green, and blue values for the corresponding pixel - # index. Instead of an integer sequence, you can use an 8-bit - # string. - # - # @def putpalette(data) - # @param data A palette sequence (either a list or a string). - - def putpalette(self, data, rawmode="RGB"): - "Put palette data into an image." - - if self.mode not in ("L", "P"): - raise ValueError("illegal image mode") - self.load() - if isinstance(data, ImagePalette.ImagePalette): - palette = ImagePalette.raw(data.rawmode, data.palette) - else: - if not isStringType(data): - data = string.join(map(chr, data), "") - palette = ImagePalette.raw(rawmode, data) - self.mode = "P" - self.palette = palette - self.palette.mode = "RGB" - self.load() # install new palette - - ## - # Modifies the pixel at the given position. The colour is given as - # a single numerical value for single-band images, and a tuple for - # multi-band images. - #
- # Note that this method is relatively slow. For more extensive - # changes, use {@link #Image.paste} or the ImageDraw module - # instead. - # - # @param xy The pixel coordinate, given as (x, y). - # @param value The pixel value. - # @see #Image.paste - # @see #Image.putdata - # @see ImageDraw - - def putpixel(self, xy, value): - "Set pixel value" - - self.load() - if self.readonly: - self._copy() - - return self.im.putpixel(xy, value) - - ## - # Returns a resized copy of this image. - # - # @def resize(size, filter=NEAREST) - # @param size The requested size in pixels, as a 2-tuple: - # (width, height). - # @param filter An optional resampling filter. This can be - # one of NEAREST (use nearest neighbour), BILINEAR - # (linear interpolation in a 2x2 environment), BICUBIC - # (cubic spline interpolation in a 4x4 environment), or - # ANTIALIAS (a high-quality downsampling filter). - # If omitted, or if the image has mode "1" or "P", it is - # set NEAREST. - # @return An Image object. - - def resize(self, size, resample=NEAREST): - "Resize image" - - if resample not in (NEAREST, BILINEAR, BICUBIC, ANTIALIAS): - raise ValueError("unknown resampling filter") - - self.load() - - if self.mode in ("1", "P"): - resample = NEAREST - - if resample == ANTIALIAS: - # requires stretch support (imToolkit & PIL 1.1.3) - try: - im = self.im.stretch(size, resample) - except AttributeError: - raise ValueError("unsupported resampling filter") - else: - im = self.im.resize(size, resample) - - return self._new(im) - - ## - # Returns a rotated copy of this image. This method returns a - # copy of this image, rotated the given number of degrees counter - # clockwise around its centre. - # - # @def rotate(angle, filter=NEAREST) - # @param angle In degrees counter clockwise. - # @param filter An optional resampling filter. This can be - # one of NEAREST (use nearest neighbour), BILINEAR - # (linear interpolation in a 2x2 environment), or BICUBIC - # (cubic spline interpolation in a 4x4 environment). - # If omitted, or if the image has mode "1" or "P", it is - # set NEAREST. - # @param expand Optional expansion flag. If true, expands the output - # image to make it large enough to hold the entire rotated image. - # If false or omitted, make the output image the same size as the - # input image. - # @return An Image object. - - def rotate(self, angle, resample=NEAREST, expand=0): - "Rotate image. Angle given as degrees counter-clockwise." - - if expand: - import math - angle = -angle * math.pi / 180 - matrix = [ - math.cos(angle), math.sin(angle), 0.0, - -math.sin(angle), math.cos(angle), 0.0 - ] - def transform(x, y, (a, b, c, d, e, f)=matrix): - return a*x + b*y + c, d*x + e*y + f - - # calculate output size - w, h = self.size - xx = [] - yy = [] - for x, y in ((0, 0), (w, 0), (w, h), (0, h)): - x, y = transform(x, y) - xx.append(x) - yy.append(y) - w = int(math.ceil(max(xx)) - math.floor(min(xx))) - h = int(math.ceil(max(yy)) - math.floor(min(yy))) - - # adjust center - x, y = transform(w / 2.0, h / 2.0) - matrix[2] = self.size[0] / 2.0 - x - matrix[5] = self.size[1] / 2.0 - y - - return self.transform((w, h), AFFINE, matrix, resample) - - if resample not in (NEAREST, BILINEAR, BICUBIC): - raise ValueError("unknown resampling filter") - - self.load() - - if self.mode in ("1", "P"): - resample = NEAREST - - return self._new(self.im.rotate(angle, resample)) - - ## - # Saves this image under the given filename. If no format is - # specified, the format to use is determined from the filename - # extension, if possible. - #
- # Keyword options can be used to provide additional instructions - # to the writer. If a writer doesn't recognise an option, it is - # silently ignored. The available options are described later in - # this handbook. - #
- # You can use a file object instead of a filename. In this case, - # you must always specify the format. The file object must - # implement the seek, tell, and write - # methods, and be opened in binary mode. - # - # @def save(file, format=None, **options) - # @param file File name or file object. - # @param format Optional format override. If omitted, the - # format to use is determined from the filename extension. - # If a file object was used instead of a filename, this - # parameter should always be used. - # @param **options Extra parameters to the image writer. - # @return None - # @exception KeyError If the output format could not be determined - # from the file name. Use the format option to solve this. - # @exception IOError If the file could not be written. The file - # may have been created, and may contain partial data. - - def save(self, fp, format=None, **params): - "Save image to file or stream" - - if isStringType(fp): - filename = fp - else: - if hasattr(fp, "name") and isStringType(fp.name): - filename = fp.name - else: - filename = "" - - # may mutate self! - self.load() - - self.encoderinfo = params - self.encoderconfig = () - - preinit() - - ext = string.lower(os.path.splitext(filename)[1]) - - if not format: - try: - format = EXTENSION[ext] - except KeyError: - init() - try: - format = EXTENSION[ext] - except KeyError: - raise KeyError(ext) # unknown extension - - try: - save_handler = SAVE[string.upper(format)] - except KeyError: - init() - save_handler = SAVE[string.upper(format)] # unknown format - - if isStringType(fp): - import __builtin__ - fp = __builtin__.open(fp, "wb") - close = 1 - else: - close = 0 - - try: - save_handler(self, fp, filename) - finally: - # do what we can to clean up - if close: - fp.close() - - ## - # Seeks to the given frame in this sequence file. If you seek - # beyond the end of the sequence, the method raises an - # EOFError exception. When a sequence file is opened, the - # library automatically seeks to frame 0. - #
- # Note that in the current version of the library, most sequence - # formats only allows you to seek to the next frame. - # - # @param frame Frame number, starting at 0. - # @exception EOFError If the call attempts to seek beyond the end - # of the sequence. - # @see #Image.tell - - def seek(self, frame): - "Seek to given frame in sequence file" - - # overridden by file handlers - if frame != 0: - raise EOFError - - ## - # Displays this image. This method is mainly intended for - # debugging purposes. - #
- # On Unix platforms, this method saves the image to a temporary - # PPM file, and calls the xv utility. - #
- # On Windows, it saves the image to a temporary BMP file, and uses - # the standard BMP display utility to show it (usually Paint). - # - # @def show(title=None) - # @param title Optional title to use for the image window, - # where possible. - - def show(self, title=None, command=None): - "Display image (for debug purposes only)" - - _show(self, title=title, command=command) - - ## - # Split this image into individual bands. This method returns a - # tuple of individual image bands from an image. For example, - # splitting an "RGB" image creates three new images each - # containing a copy of one of the original bands (red, green, - # blue). - # - # @return A tuple containing bands. - - def split(self): - "Split image into bands" - - if self.im.bands == 1: - ims = [self.copy()] - else: - ims = [] - self.load() - for i in range(self.im.bands): - ims.append(self._new(self.im.getband(i))) - return tuple(ims) - - ## - # Returns the current frame number. - # - # @return Frame number, starting with 0. - # @see #Image.seek - - def tell(self): - "Return current frame number" - - return 0 - - ## - # Make this image into a thumbnail. This method modifies the - # image to contain a thumbnail version of itself, no larger than - # the given size. This method calculates an appropriate thumbnail - # size to preserve the aspect of the image, calls the {@link - # #Image.draft} method to configure the file reader (where - # applicable), and finally resizes the image. - #
- # Note that the bilinear and bicubic filters in the current - # version of PIL are not well-suited for thumbnail generation. - # You should use ANTIALIAS unless speed is much more - # important than quality. - #
- # Also note that this function modifies the Image object in place. - # If you need to use the full resolution image as well, apply this - # method to a {@link #Image.copy} of the original image. - # - # @param size Requested size. - # @param resample Optional resampling filter. This can be one - # of NEAREST, BILINEAR, BICUBIC, or - # ANTIALIAS (best quality). If omitted, it defaults - # to NEAREST (this will be changed to ANTIALIAS in a - # future version). - # @return None - - def thumbnail(self, size, resample=NEAREST): - "Create thumbnail representation (modifies image in place)" - - # FIXME: the default resampling filter will be changed - # to ANTIALIAS in future versions - - # preserve aspect ratio - x, y = self.size - if x > size[0]: y = max(y * size[0] / x, 1); x = size[0] - if y > size[1]: x = max(x * size[1] / y, 1); y = size[1] - size = x, y - - if size == self.size: - return - - self.draft(None, size) - - self.load() - - try: - im = self.resize(size, resample) - except ValueError: - if resample != ANTIALIAS: - raise - im = self.resize(size, NEAREST) # fallback - - self.im = im.im - self.mode = im.mode - self.size = size - - self.readonly = 0 - - # FIXME: the different tranform methods need further explanation - # instead of bloating the method docs, add a separate chapter. - - ## - # Transforms this image. This method creates a new image with the - # given size, and the same mode as the original, and copies data - # to the new image using the given transform. - #
- # @def transform(size, method, data, resample=NEAREST) - # @param size The output size. - # @param method The transformation method. This is one of - # EXTENT (cut out a rectangular subregion), AFFINE - # (affine transform), PERSPECTIVE (perspective - # transform), QUAD (map a quadrilateral to a - # rectangle), or MESH (map a number of source quadrilaterals - # in one operation). - # @param data Extra data to the transformation method. - # @param resample Optional resampling filter. It can be one of - # NEAREST (use nearest neighbour), BILINEAR - # (linear interpolation in a 2x2 environment), or - # BICUBIC (cubic spline interpolation in a 4x4 - # environment). If omitted, or if the image has mode - # "1" or "P", it is set to NEAREST. - # @return An Image object. - - def transform(self, size, method, data=None, resample=NEAREST, fill=1): - "Transform image" - - if isinstance(method, ImageTransformHandler): - return method.transform(size, self, resample=resample, fill=fill) - if hasattr(method, "getdata"): - # compatibility w. old-style transform objects - method, data = method.getdata() - if data is None: - raise ValueError("missing method data") - im = new(self.mode, size, None) - if method == MESH: - # list of quads - for box, quad in data: - im.__transformer(box, self, QUAD, quad, resample, fill) - else: - im.__transformer((0, 0)+size, self, method, data, resample, fill) - - return im - - def __transformer(self, box, image, method, data, - resample=NEAREST, fill=1): - - # FIXME: this should be turned into a lazy operation (?) - - w = box[2]-box[0] - h = box[3]-box[1] - - if method == AFFINE: - # change argument order to match implementation - data = (data[2], data[0], data[1], - data[5], data[3], data[4]) - elif method == EXTENT: - # convert extent to an affine transform - x0, y0, x1, y1 = data - xs = float(x1 - x0) / w - ys = float(y1 - y0) / h - method = AFFINE - data = (x0 + xs/2, xs, 0, y0 + ys/2, 0, ys) - elif method == PERSPECTIVE: - # change argument order to match implementation - data = (data[2], data[0], data[1], - data[5], data[3], data[4], - data[6], data[7]) - elif method == QUAD: - # quadrilateral warp. data specifies the four corners - # given as NW, SW, SE, and NE. - nw = data[0:2]; sw = data[2:4]; se = data[4:6]; ne = data[6:8] - x0, y0 = nw; As = 1.0 / w; At = 1.0 / h - data = (x0, (ne[0]-x0)*As, (sw[0]-x0)*At, - (se[0]-sw[0]-ne[0]+x0)*As*At, - y0, (ne[1]-y0)*As, (sw[1]-y0)*At, - (se[1]-sw[1]-ne[1]+y0)*As*At) - else: - raise ValueError("unknown transformation method") - - if resample not in (NEAREST, BILINEAR, BICUBIC): - raise ValueError("unknown resampling filter") - - image.load() - - self.load() - - if image.mode in ("1", "P"): - resample = NEAREST - - self.im.transform2(box, image.im, method, data, resample, fill) - - ## - # Returns a flipped or rotated copy of this image. - # - # @param method One of FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM, - # ROTATE_90, ROTATE_180, or ROTATE_270. - - def transpose(self, method): - "Transpose image (flip or rotate in 90 degree steps)" - - self.load() - im = self.im.transpose(method) - return self._new(im) - -# -------------------------------------------------------------------- -# Lazy operations - -class _ImageCrop(Image): - - def __init__(self, im, box): - - Image.__init__(self) - - x0, y0, x1, y1 = box - if x1 < x0: - x1 = x0 - if y1 < y0: - y1 = y0 - - self.mode = im.mode - self.size = x1-x0, y1-y0 - - self.__crop = x0, y0, x1, y1 - - self.im = im.im - - def load(self): - - # lazy evaluation! - if self.__crop: - self.im = self.im.crop(self.__crop) - self.__crop = None - - if self.im: - return self.im.pixel_access(self.readonly) - - # FIXME: future versions should optimize crop/paste - # sequences! - -# -------------------------------------------------------------------- -# Abstract handlers. - -class ImagePointHandler: - # used as a mixin by point transforms (for use with im.point) - pass - -class ImageTransformHandler: - # used as a mixin by geometry transforms (for use with im.transform) - pass - -# -------------------------------------------------------------------- -# Factories - -# -# Debugging - -def _wedge(): - "Create greyscale wedge (for debugging only)" - - return Image()._new(core.wedge("L")) - -## -# Creates a new image with the given mode and size. -# -# @param mode The mode to use for the new image. -# @param size A 2-tuple, containing (width, height) in pixels. -# @param color What colour to use for the image. Default is black. -# If given, this should be a single integer or floating point value -# for single-band modes, and a tuple for multi-band modes (one value -# per band). When creating RGB images, you can also use colour -# strings as supported by the ImageColor module. If the colour is -# None, the image is not initialised. -# @return An Image object. - -def new(mode, size, color=0): - "Create a new image" - - if color is None: - # don't initialize - return Image()._new(core.new(mode, size)) - - if isStringType(color): - # css3-style specifier - - import ImageColor - color = ImageColor.getcolor(color, mode) - - return Image()._new(core.fill(mode, size, color)) - -## -# Creates an image memory from pixel data in a string. -#
-# In its simplest form, this function takes three arguments -# (mode, size, and unpacked pixel data). -#
-# You can also use any pixel decoder supported by PIL. For more -# information on available decoders, see the section Writing Your Own File Decoder. -#
-# Note that this function decodes pixel data only, not entire images. -# If you have an entire image in a string, wrap it in a -# StringIO object, and use {@link #open} to load it. -# -# @param mode The image mode. -# @param size The image size. -# @param data An 8-bit string containing raw data for the given mode. -# @param decoder_name What decoder to use. -# @param *args Additional parameters for the given decoder. -# @return An Image object. - -def fromstring(mode, size, data, decoder_name="raw", *args): - "Load image from string" - - # may pass tuple instead of argument list - if len(args) == 1 and isTupleType(args[0]): - args = args[0] - - if decoder_name == "raw" and args == (): - args = mode - - im = new(mode, size) - im.fromstring(data, decoder_name, args) - return im - -## -# (New in 1.1.4) Creates an image memory from pixel data in a string -# or byte buffer. -#
-# This function is similar to {@link #fromstring}, but uses data in -# the byte buffer, where possible. This means that changes to the -# original buffer object are reflected in this image). Not all modes -# can share memory; supported modes include "L", "RGBX", "RGBA", and -# "CMYK". -#
-# Note that this function decodes pixel data only, not entire images. -# If you have an entire image file in a string, wrap it in a -# StringIO object, and use {@link #open} to load it. -#
-# In the current version, the default parameters used for the "raw"
-# decoder differs from that used for {@link fromstring}. This is a
-# bug, and will probably be fixed in a future release. The current
-# release issues a warning if you do this; to disable the warning,
-# you should provide the full set of parameters. See below for
-# details.
-#
-# @param mode The image mode.
-# @param size The image size.
-# @param data An 8-bit string or other buffer object containing raw
-# data for the given mode.
-# @param decoder_name What decoder to use.
-# @param *args Additional parameters for the given decoder. For the
-# default encoder ("raw"), it's recommended that you provide the
-# full set of parameters:
-# frombuffer(mode, size, data, "raw", mode, 0, 1).
-# @return An Image object.
-# @since 1.1.4
-
-def frombuffer(mode, size, data, decoder_name="raw", *args):
- "Load image from string or buffer"
-
- # may pass tuple instead of argument list
- if len(args) == 1 and isTupleType(args[0]):
- args = args[0]
-
- if decoder_name == "raw":
- if args == ():
- if warnings:
- warnings.warn(
- "the frombuffer defaults may change in a future release; "
- "for portability, change the call to read:\n"
- " frombuffer(mode, size, data, 'raw', mode, 0, 1)",
- RuntimeWarning, stacklevel=2
- )
- args = mode, 0, -1 # may change to (mode, 0, 1) post-1.1.6
- if args[0] in _MAPMODES:
- im = new(mode, (1,1))
- im = im._new(
- core.map_buffer(data, size, decoder_name, None, 0, args)
- )
- im.readonly = 1
- return im
-
- return fromstring(mode, size, data, decoder_name, args)
-
-
-##
-# (New in 1.1.6) Creates an image memory from an object exporting
-# the array interface (using the buffer protocol).
-#
-# If obj is not contiguous, then the tostring method is called
-# and {@link frombuffer} is used.
-#
-# @param obj Object with array interface
-# @param mode Mode to use (will be determined from type if None)
-# @return An image memory.
-
-def fromarray(obj, mode=None):
- arr = obj.__array_interface__
- shape = arr['shape']
- ndim = len(shape)
- try:
- strides = arr['strides']
- except KeyError:
- strides = None
- if mode is None:
- try:
- typekey = (1, 1) + shape[2:], arr['typestr']
- mode, rawmode = _fromarray_typemap[typekey]
- except KeyError:
- # print typekey
- raise TypeError("Cannot handle this data type")
- else:
- rawmode = mode
- if mode in ["1", "L", "I", "P", "F"]:
- ndmax = 2
- elif mode == "RGB":
- ndmax = 3
- else:
- ndmax = 4
- if ndim > ndmax:
- raise ValueError("Too many dimensions.")
-
- size = shape[1], shape[0]
- if strides is not None:
- obj = obj.tostring()
-
- return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
-
-_fromarray_typemap = {
- # (shape, typestr) => mode, rawmode
- # first two members of shape are set to one
- # ((1, 1), "|b1"): ("1", "1"), # broken
- ((1, 1), "|u1"): ("L", "L"),
- ((1, 1), "|i1"): ("I", "I;8"),
- ((1, 1), "
-# This is a lazy operation; this function identifies the file, but the
-# actual image data is not read from the file until you try to process
-# the data (or call the {@link #Image.load} method).
-#
-# @def open(file, mode="r")
-# @param file A filename (string) or a file object. The file object
-# must implement read, seek, and tell methods,
-# and be opened in binary mode.
-# @param mode The mode. If given, this argument must be "r".
-# @return An Image object.
-# @exception IOError If the file cannot be found, or the image cannot be
-# opened and identified.
-# @see #new
-
-def open(fp, mode="r"):
- "Open an image file, without loading the raster data"
-
- if mode != "r":
- raise ValueError("bad mode")
-
- if isStringType(fp):
- import __builtin__
- filename = fp
- fp = __builtin__.open(fp, "rb")
- else:
- filename = ""
-
- prefix = fp.read(16)
-
- preinit()
-
- for i in ID:
- try:
- factory, accept = OPEN[i]
- if not accept or accept(prefix):
- fp.seek(0)
- return factory(fp, filename)
- except (SyntaxError, IndexError, TypeError):
- pass
-
- if init():
-
- for i in ID:
- try:
- factory, accept = OPEN[i]
- if not accept or accept(prefix):
- fp.seek(0)
- return factory(fp, filename)
- except (SyntaxError, IndexError, TypeError):
- pass
-
- raise IOError("cannot identify image file")
-
-#
-# Image processing.
-
-##
-# Creates a new image by interpolating between two input images, using
-# a constant alpha.
-#
-#
-# A stub loader is an image loader that can identify files of a
-# certain format, but relies on external code to load the file.
-
-class StubImageFile(ImageFile):
- "Base class for stub image loaders."
-
- def _open(self):
- raise NotImplementedError(
- "StubImageFile subclass must implement _open"
- )
-
- def load(self):
- loader = self._load()
- if loader is None:
- raise IOError("cannot find loader for this %s file" % self.format)
- image = loader.load(self)
- assert image is not None
- # become the other object (!)
- self.__class__ = image.__class__
- self.__dict__ = image.__dict__
-
- ##
- # (Hook) Find actual image loader.
-
- def _load(self):
- raise NotImplementedError(
- "StubImageFile subclass must implement _load"
- )
-
-##
-# (Internal) Support class for the Parser file.
-
-class _ParserFile:
- # parser support class.
-
- def __init__(self, data):
- self.data = data
- self.offset = 0
-
- def close(self):
- self.data = self.offset = None
-
- def tell(self):
- return self.offset
-
- def seek(self, offset, whence=0):
- if whence == 0:
- self.offset = offset
- elif whence == 1:
- self.offset = self.offset + offset
- else:
- # force error in Image.open
- raise IOError("illegal argument to seek")
-
- def read(self, bytes=0):
- pos = self.offset
- if bytes:
- data = self.data[pos:pos+bytes]
- else:
- data = self.data[pos:]
- self.offset = pos + len(data)
- return data
-
- def readline(self):
- # FIXME: this is slow!
- s = ""
- while 1:
- c = self.read(1)
- if not c:
- break
- s = s + c
- if c == "\n":
- break
- return s
-
-##
-# Incremental image parser. This class implements the standard
-# feed/close consumer interface.
-
-class Parser:
-
- incremental = None
- image = None
- data = None
- decoder = None
- finished = 0
-
- ##
- # (Consumer) Reset the parser. Note that you can only call this
- # method immediately after you've created a parser; parser
- # instances cannot be reused.
-
- def reset(self):
- assert self.data is None, "cannot reuse parsers"
-
- ##
- # (Consumer) Feed data to the parser.
- #
- # @param data A string buffer.
- # @exception IOError If the parser failed to parse the image file.
-
- def feed(self, data):
- # collect data
-
- if self.finished:
- return
-
- if self.data is None:
- self.data = data
- else:
- self.data = self.data + data
-
- # parse what we have
- if self.decoder:
-
- if self.offset > 0:
- # skip header
- skip = min(len(self.data), self.offset)
- self.data = self.data[skip:]
- self.offset = self.offset - skip
- if self.offset > 0 or not self.data:
- return
-
- n, e = self.decoder.decode(self.data)
-
- if n < 0:
- # end of stream
- self.data = None
- self.finished = 1
- if e < 0:
- # decoding error
- self.image = None
- raise_ioerror(e)
- else:
- # end of image
- return
- self.data = self.data[n:]
-
- elif self.image:
-
- # if we end up here with no decoder, this file cannot
- # be incrementally parsed. wait until we've gotten all
- # available data
- pass
-
- else:
-
- # attempt to open this file
- try:
- try:
- fp = _ParserFile(self.data)
- im = Image.open(fp)
- finally:
- fp.close() # explicitly close the virtual file
- except IOError:
- pass # not enough data
- else:
- flag = hasattr(im, "load_seek") or hasattr(im, "load_read")
- if flag or len(im.tile) != 1:
- # custom load code, or multiple tiles
- self.decode = None
- else:
- # initialize decoder
- im.load_prepare()
- d, e, o, a = im.tile[0]
- im.tile = []
- self.decoder = Image._getdecoder(
- im.mode, d, a, im.decoderconfig
- )
- self.decoder.setimage(im.im, e)
-
- # calculate decoder offset
- self.offset = o
- if self.offset <= len(self.data):
- self.data = self.data[self.offset:]
- self.offset = 0
-
- self.image = im
-
- ##
- # (Consumer) Close the stream.
- #
- # @return An image object.
- # @exception IOError If the parser failed to parse the image file.
-
- def close(self):
- # finish decoding
- if self.decoder:
- # get rid of what's left in the buffers
- self.feed("")
- self.data = self.decoder = None
- if not self.finished:
- raise IOError("image was incomplete")
- if not self.image:
- raise IOError("cannot parse this image")
- if self.data:
- # incremental parsing not possible; reopen the file
- # not that we have all data
- try:
- fp = _ParserFile(self.data)
- self.image = Image.open(fp)
- finally:
- self.image.load()
- fp.close() # explicitly close the virtual file
- return self.image
-
-# --------------------------------------------------------------------
-
-##
-# (Helper) Save image body to file.
-#
-# @param im Image object.
-# @param fp File object.
-# @param tile Tile list.
-
-def _save(im, fp, tile):
- "Helper to save image based on tile list"
-
- im.load()
- if not hasattr(im, "encoderconfig"):
- im.encoderconfig = ()
- tile.sort(_tilesort)
- # FIXME: make MAXBLOCK a configuration parameter
- bufsize = max(MAXBLOCK, im.size[0] * 4) # see RawEncode.c
- try:
- fh = fp.fileno()
- fp.flush()
- except AttributeError:
- # compress to Python file-compatible object
- for e, b, o, a in tile:
- e = Image._getencoder(im.mode, e, a, im.encoderconfig)
- if o > 0:
- fp.seek(o, 0)
- e.setimage(im.im, b)
- while 1:
- l, s, d = e.encode(bufsize)
- fp.write(d)
- if s:
- break
- if s < 0:
- raise IOError("encoder error %d when writing image file" % s)
- else:
- # slight speedup: compress to real file object
- for e, b, o, a in tile:
- e = Image._getencoder(im.mode, e, a, im.encoderconfig)
- if o > 0:
- fp.seek(o, 0)
- e.setimage(im.im, b)
- s = e.encode_to_file(fh, bufsize)
- if s < 0:
- raise IOError("encoder error %d when writing image file" % s)
- try:
- fp.flush()
- except: pass
-
-
-##
-# Reads large blocks in a safe way. Unlike fp.read(n), this function
-# doesn't trust the user. If the requested size is larger than
-# SAFEBLOCK, the file is read block by block.
-#
-# @param fp File handle. Must implement a read method.
-# @param size Number of bytes to read.
-# @return A string containing up to size bytes of data.
-
-def _safe_read(fp, size):
- if size <= 0:
- return ""
- if size <= SAFEBLOCK:
- return fp.read(size)
- data = []
- while size > 0:
- block = fp.read(min(size, SAFEBLOCK))
- if not block:
- break
- data.append(block)
- size = size - len(block)
- return string.join(data, "")
diff --git a/image_occlusion_enhanced/Imaging/PIL/ImageMode.py b/image_occlusion_enhanced/Imaging/PIL/ImageMode.py
deleted file mode 100644
index 1d5df1c6..00000000
--- a/image_occlusion_enhanced/Imaging/PIL/ImageMode.py
+++ /dev/null
@@ -1,50 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# standard mode descriptors
-#
-# History:
-# 2006-03-20 fl Added
-#
-# Copyright (c) 2006 by Secret Labs AB.
-# Copyright (c) 2006 by Fredrik Lundh.
-#
-# See the README file for information on usage and redistribution.
-#
-
-# mode descriptor cache
-_modes = {}
-
-##
-# Wrapper for mode strings.
-
-class ModeDescriptor:
-
- def __init__(self, mode, bands, basemode, basetype):
- self.mode = mode
- self.bands = bands
- self.basemode = basemode
- self.basetype = basetype
-
- def __str__(self):
- return self.mode
-
-##
-# Gets a mode descriptor for the given mode.
-
-def getmode(mode):
- if not _modes:
- # initialize mode cache
- import Image
- # core modes
- for m, (basemode, basetype, bands) in Image._MODEINFO.items():
- _modes[m] = ModeDescriptor(m, bands, basemode, basetype)
- # extra experimental modes
- _modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L")
- _modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L")
- # mapping modes
- _modes["I;16"] = ModeDescriptor("I;16", "I", "L", "L")
- _modes["I;16L"] = ModeDescriptor("I;16L", "I", "L", "L")
- _modes["I;16B"] = ModeDescriptor("I;16B", "I", "L", "L")
- return _modes[mode]
diff --git a/image_occlusion_enhanced/Imaging/PIL/ImagePalette.py b/image_occlusion_enhanced/Imaging/PIL/ImagePalette.py
deleted file mode 100644
index 6efee299..00000000
--- a/image_occlusion_enhanced/Imaging/PIL/ImagePalette.py
+++ /dev/null
@@ -1,184 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# image palette object
-#
-# History:
-# 1996-03-11 fl Rewritten.
-# 1997-01-03 fl Up and running.
-# 1997-08-23 fl Added load hack
-# 2001-04-16 fl Fixed randint shadow bug in random()
-#
-# Copyright (c) 1997-2001 by Secret Labs AB
-# Copyright (c) 1996-1997 by Fredrik Lundh
-#
-# See the README file for information on usage and redistribution.
-#
-
-import array
-import Image, ImageColor
-
-##
-# Colour palette wrapper for palette mapped images.
-
-class ImagePalette:
- "Colour palette for palette mapped images"
-
- def __init__(self, mode = "RGB", palette = None):
- self.mode = mode
- self.rawmode = None # if set, palette contains raw data
- self.palette = palette or range(256)*len(self.mode)
- self.colors = {}
- self.dirty = None
- if len(self.mode)*256 != len(self.palette):
- raise ValueError, "wrong palette size"
-
- def getdata(self):
- # experimental: get palette contents in format suitable
- # for the low-level im.putpalette primitive
- if self.rawmode:
- return self.rawmode, self.palette
- return self.mode + ";L", self.tostring()
-
- def tostring(self):
- # experimental: convert palette to string
- if self.rawmode:
- raise ValueError("palette contains raw palette data")
- if Image.isStringType(self.palette):
- return self.palette
- return array.array("B", self.palette).tostring()
-
- def getcolor(self, color):
- # experimental: given an rgb tuple, allocate palette entry
- if self.rawmode:
- raise ValueError("palette contains raw palette data")
- if Image.isTupleType(color):
- try:
- return self.colors[color]
- except KeyError:
- # allocate new color slot
- if Image.isStringType(self.palette):
- self.palette = map(int, self.palette)
- index = len(self.colors)
- if index >= 256:
- raise ValueError("cannot allocate more than 256 colors")
- self.colors[color] = index
- self.palette[index] = color[0]
- self.palette[index+256] = color[1]
- self.palette[index+512] = color[2]
- self.dirty = 1
- return index
- else:
- raise ValueError("unknown color specifier: %r" % color)
-
- def save(self, fp):
- # (experimental) save palette to text file
- if self.rawmode:
- raise ValueError("palette contains raw palette data")
- if type(fp) == type(""):
- fp = open(fp, "w")
- fp.write("# Palette\n")
- fp.write("# Mode: %s\n" % self.mode)
- for i in range(256):
- fp.write("%d" % i)
- for j in range(i, len(self.palette), 256):
- fp.write(" %d" % self.palette[j])
- fp.write("\n")
- fp.close()
-
-# --------------------------------------------------------------------
-# Internal
-
-def raw(rawmode, data):
- palette = ImagePalette()
- palette.rawmode = rawmode
- palette.palette = data
- palette.dirty = 1
- return palette
-
-# --------------------------------------------------------------------
-# Factories
-
-def _make_linear_lut(black, white):
- lut = []
- if black == 0:
- for i in range(256):
- lut.append(white*i/255)
- else:
- raise NotImplementedError # FIXME
- return lut
-
-def _make_gamma_lut(exp, mode="RGB"):
- lut = []
- for i in range(256):
- lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5))
- return lut
-
-def new(mode, data):
- return Image.core.new_palette(mode, data)
-
-def negative(mode="RGB"):
- palette = range(256)
- palette.reverse()
- return ImagePalette(mode, palette * len(mode))
-
-def random(mode="RGB"):
- from random import randint
- palette = []
- for i in range(256*len(mode)):
- palette.append(randint(0, 255))
- return ImagePalette(mode, palette)
-
-def sepia(white="#fff0c0"):
- r, g, b = ImageColor.getrgb(white)
- r = _make_linear_lut(0, r)
- g = _make_linear_lut(0, g)
- b = _make_linear_lut(0, b)
- return ImagePalette("RGB", r + g + b)
-
-def wedge(mode="RGB"):
- return ImagePalette(mode, range(256) * len(mode))
-
-def load(filename):
-
- # FIXME: supports GIMP gradients only
-
- fp = open(filename, "rb")
-
- lut = None
-
- if not lut:
- try:
- import GimpPaletteFile
- fp.seek(0)
- p = GimpPaletteFile.GimpPaletteFile(fp)
- lut = p.getpalette()
- except (SyntaxError, ValueError):
- pass
-
- if not lut:
- try:
- import GimpGradientFile
- fp.seek(0)
- p = GimpGradientFile.GimpGradientFile(fp)
- lut = p.getpalette()
- except (SyntaxError, ValueError):
- pass
-
- if not lut:
- try:
- import PaletteFile
- fp.seek(0)
- p = PaletteFile.PaletteFile(fp)
- lut = p.getpalette()
- except (SyntaxError, ValueError):
- pass
-
- if not lut:
- raise IOError, "cannot load palette"
-
- return lut # data, rawmode
-
-
-# add some psuedocolour palettes as well
diff --git a/image_occlusion_enhanced/Imaging/PIL/JpegImagePlugin.py b/image_occlusion_enhanced/Imaging/PIL/JpegImagePlugin.py
deleted file mode 100644
index 933abf3c..00000000
--- a/image_occlusion_enhanced/Imaging/PIL/JpegImagePlugin.py
+++ /dev/null
@@ -1,492 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# JPEG (JFIF) file handling
-#
-# See "Digital Compression and Coding of Continous-Tone Still Images,
-# Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1)
-#
-# History:
-# 1995-09-09 fl Created
-# 1995-09-13 fl Added full parser
-# 1996-03-25 fl Added hack to use the IJG command line utilities
-# 1996-05-05 fl Workaround Photoshop 2.5 CMYK polarity bug
-# 1996-05-28 fl Added draft support, JFIF version (0.1)
-# 1996-12-30 fl Added encoder options, added progression property (0.2)
-# 1997-08-27 fl Save mode 1 images as BW (0.3)
-# 1998-07-12 fl Added YCbCr to draft and save methods (0.4)
-# 1998-10-19 fl Don't hang on files using 16-bit DQT's (0.4.1)
-# 2001-04-16 fl Extract DPI settings from JFIF files (0.4.2)
-# 2002-07-01 fl Skip pad bytes before markers; identify Exif files (0.4.3)
-# 2003-04-25 fl Added experimental EXIF decoder (0.5)
-# 2003-06-06 fl Added experimental EXIF GPSinfo decoder
-# 2003-09-13 fl Extract COM markers
-# 2009-09-06 fl Added icc_profile support (from Florian Hoech)
-# 2009-03-06 fl Changed CMYK handling; always use Adobe polarity (0.6)
-# 2009-03-08 fl Added subsampling support (from Justin Huff).
-#
-# Copyright (c) 1997-2003 by Secret Labs AB.
-# Copyright (c) 1995-1996 by Fredrik Lundh.
-#
-# See the README file for information on usage and redistribution.
-#
-
-__version__ = "0.6"
-
-import array, struct
-import string
-import Image, ImageFile
-
-def i16(c,o=0):
- return ord(c[o+1]) + (ord(c[o])<<8)
-
-def i32(c,o=0):
- return ord(c[o+3]) + (ord(c[o+2])<<8) + (ord(c[o+1])<<16) + (ord(c[o])<<24)
-
-#
-# Parser
-
-def Skip(self, marker):
- n = i16(self.fp.read(2))-2
- ImageFile._safe_read(self.fp, n)
-
-def APP(self, marker):
- #
- # Application marker. Store these in the APP dictionary.
- # Also look for well-known application markers.
-
- n = i16(self.fp.read(2))-2
- s = ImageFile._safe_read(self.fp, n)
-
- app = "APP%d" % (marker&15)
-
- self.app[app] = s # compatibility
- self.applist.append((app, s))
-
- if marker == 0xFFE0 and s[:4] == "JFIF":
- # extract JFIF information
- self.info["jfif"] = version = i16(s, 5) # version
- self.info["jfif_version"] = divmod(version, 256)
- # extract JFIF properties
- try:
- jfif_unit = ord(s[7])
- jfif_density = i16(s, 8), i16(s, 10)
- except:
- pass
- else:
- if jfif_unit == 1:
- self.info["dpi"] = jfif_density
- self.info["jfif_unit"] = jfif_unit
- self.info["jfif_density"] = jfif_density
- elif marker == 0xFFE1 and s[:5] == "Exif\0":
- # extract Exif information (incomplete)
- self.info["exif"] = s # FIXME: value will change
- elif marker == 0xFFE2 and s[:5] == "FPXR\0":
- # extract FlashPix information (incomplete)
- self.info["flashpix"] = s # FIXME: value will change
- elif marker == 0xFFE2 and s[:12] == "ICC_PROFILE\0":
- # Since an ICC profile can be larger than the maximum size of
- # a JPEG marker (64K), we need provisions to split it into
- # multiple markers. The format defined by the ICC specifies
- # one or more APP2 markers containing the following data:
- # Identifying string ASCII "ICC_PROFILE\0" (12 bytes)
- # Marker sequence number 1, 2, etc (1 byte)
- # Number of markers Total of APP2's used (1 byte)
- # Profile data (remainder of APP2 data)
- # Decoders should use the marker sequence numbers to
- # reassemble the profile, rather than assuming that the APP2
- # markers appear in the correct sequence.
- self.icclist.append(s)
- elif marker == 0xFFEE and s[:5] == "Adobe":
- self.info["adobe"] = i16(s, 5)
- # extract Adobe custom properties
- try:
- adobe_transform = ord(s[1])
- except:
- pass
- else:
- self.info["adobe_transform"] = adobe_transform
-
-def COM(self, marker):
- #
- # Comment marker. Store these in the APP dictionary.
-
- n = i16(self.fp.read(2))-2
- s = ImageFile._safe_read(self.fp, n)
-
- self.app["COM"] = s # compatibility
- self.applist.append(("COM", s))
-
-def SOF(self, marker):
- #
- # Start of frame marker. Defines the size and mode of the
- # image. JPEG is colour blind, so we use some simple
- # heuristics to map the number of layers to an appropriate
- # mode. Note that this could be made a bit brighter, by
- # looking for JFIF and Adobe APP markers.
-
- n = i16(self.fp.read(2))-2
- s = ImageFile._safe_read(self.fp, n)
- self.size = i16(s[3:]), i16(s[1:])
-
- self.bits = ord(s[0])
- if self.bits != 8:
- raise SyntaxError("cannot handle %d-bit layers" % self.bits)
-
- self.layers = ord(s[5])
- if self.layers == 1:
- self.mode = "L"
- elif self.layers == 3:
- self.mode = "RGB"
- elif self.layers == 4:
- self.mode = "CMYK"
- else:
- raise SyntaxError("cannot handle %d-layer images" % self.layers)
-
- if marker in [0xFFC2, 0xFFC6, 0xFFCA, 0xFFCE]:
- self.info["progressive"] = self.info["progression"] = 1
-
- if self.icclist:
- # fixup icc profile
- self.icclist.sort() # sort by sequence number
- if ord(self.icclist[0][13]) == len(self.icclist):
- profile = []
- for p in self.icclist:
- profile.append(p[14:])
- icc_profile = string.join(profile, "")
- else:
- icc_profile = None # wrong number of fragments
- self.info["icc_profile"] = icc_profile
- self.icclist = None
-
- for i in range(6, len(s), 3):
- t = s[i:i+3]
- # 4-tuples: id, vsamp, hsamp, qtable
- self.layer.append((t[0], ord(t[1])/16, ord(t[1])&15, ord(t[2])))
-
-def DQT(self, marker):
- #
- # Define quantization table. Support baseline 8-bit tables
- # only. Note that there might be more than one table in
- # each marker.
-
- # FIXME: The quantization tables can be used to estimate the
- # compression quality.
-
- n = i16(self.fp.read(2))-2
- s = ImageFile._safe_read(self.fp, n)
- while len(s):
- if len(s) < 65:
- raise SyntaxError("bad quantization table marker")
- v = ord(s[0])
- if v/16 == 0:
- self.quantization[v&15] = array.array("b", s[1:65])
- s = s[65:]
- else:
- return # FIXME: add code to read 16-bit tables!
- # raise SyntaxError, "bad quantization table element size"
-
-
-#
-# JPEG marker table
-
-MARKER = {
- 0xFFC0: ("SOF0", "Baseline DCT", SOF),
- 0xFFC1: ("SOF1", "Extended Sequential DCT", SOF),
- 0xFFC2: ("SOF2", "Progressive DCT", SOF),
- 0xFFC3: ("SOF3", "Spatial lossless", SOF),
- 0xFFC4: ("DHT", "Define Huffman table", Skip),
- 0xFFC5: ("SOF5", "Differential sequential DCT", SOF),
- 0xFFC6: ("SOF6", "Differential progressive DCT", SOF),
- 0xFFC7: ("SOF7", "Differential spatial", SOF),
- 0xFFC8: ("JPG", "Extension", None),
- 0xFFC9: ("SOF9", "Extended sequential DCT (AC)", SOF),
- 0xFFCA: ("SOF10", "Progressive DCT (AC)", SOF),
- 0xFFCB: ("SOF11", "Spatial lossless DCT (AC)", SOF),
- 0xFFCC: ("DAC", "Define arithmetic coding conditioning", Skip),
- 0xFFCD: ("SOF13", "Differential sequential DCT (AC)", SOF),
- 0xFFCE: ("SOF14", "Differential progressive DCT (AC)", SOF),
- 0xFFCF: ("SOF15", "Differential spatial (AC)", SOF),
- 0xFFD0: ("RST0", "Restart 0", None),
- 0xFFD1: ("RST1", "Restart 1", None),
- 0xFFD2: ("RST2", "Restart 2", None),
- 0xFFD3: ("RST3", "Restart 3", None),
- 0xFFD4: ("RST4", "Restart 4", None),
- 0xFFD5: ("RST5", "Restart 5", None),
- 0xFFD6: ("RST6", "Restart 6", None),
- 0xFFD7: ("RST7", "Restart 7", None),
- 0xFFD8: ("SOI", "Start of image", None),
- 0xFFD9: ("EOI", "End of image", None),
- 0xFFDA: ("SOS", "Start of scan", Skip),
- 0xFFDB: ("DQT", "Define quantization table", DQT),
- 0xFFDC: ("DNL", "Define number of lines", Skip),
- 0xFFDD: ("DRI", "Define restart interval", Skip),
- 0xFFDE: ("DHP", "Define hierarchical progression", SOF),
- 0xFFDF: ("EXP", "Expand reference component", Skip),
- 0xFFE0: ("APP0", "Application segment 0", APP),
- 0xFFE1: ("APP1", "Application segment 1", APP),
- 0xFFE2: ("APP2", "Application segment 2", APP),
- 0xFFE3: ("APP3", "Application segment 3", APP),
- 0xFFE4: ("APP4", "Application segment 4", APP),
- 0xFFE5: ("APP5", "Application segment 5", APP),
- 0xFFE6: ("APP6", "Application segment 6", APP),
- 0xFFE7: ("APP7", "Application segment 7", APP),
- 0xFFE8: ("APP8", "Application segment 8", APP),
- 0xFFE9: ("APP9", "Application segment 9", APP),
- 0xFFEA: ("APP10", "Application segment 10", APP),
- 0xFFEB: ("APP11", "Application segment 11", APP),
- 0xFFEC: ("APP12", "Application segment 12", APP),
- 0xFFED: ("APP13", "Application segment 13", APP),
- 0xFFEE: ("APP14", "Application segment 14", APP),
- 0xFFEF: ("APP15", "Application segment 15", APP),
- 0xFFF0: ("JPG0", "Extension 0", None),
- 0xFFF1: ("JPG1", "Extension 1", None),
- 0xFFF2: ("JPG2", "Extension 2", None),
- 0xFFF3: ("JPG3", "Extension 3", None),
- 0xFFF4: ("JPG4", "Extension 4", None),
- 0xFFF5: ("JPG5", "Extension 5", None),
- 0xFFF6: ("JPG6", "Extension 6", None),
- 0xFFF7: ("JPG7", "Extension 7", None),
- 0xFFF8: ("JPG8", "Extension 8", None),
- 0xFFF9: ("JPG9", "Extension 9", None),
- 0xFFFA: ("JPG10", "Extension 10", None),
- 0xFFFB: ("JPG11", "Extension 11", None),
- 0xFFFC: ("JPG12", "Extension 12", None),
- 0xFFFD: ("JPG13", "Extension 13", None),
- 0xFFFE: ("COM", "Comment", COM)
-}
-
-
-def _accept(prefix):
- return prefix[0] == "\377"
-
-##
-# Image plugin for JPEG and JFIF images.
-
-class JpegImageFile(ImageFile.ImageFile):
-
- format = "JPEG"
- format_description = "JPEG (ISO 10918)"
-
- def _open(self):
-
- s = self.fp.read(1)
-
- if ord(s[0]) != 255:
- raise SyntaxError("not a JPEG file")
-
- # Create attributes
- self.bits = self.layers = 0
-
- # JPEG specifics (internal)
- self.layer = []
- self.huffman_dc = {}
- self.huffman_ac = {}
- self.quantization = {}
- self.app = {} # compatibility
- self.applist = []
- self.icclist = []
-
- while 1:
-
- s = s + self.fp.read(1)
-
- i = i16(s)
-
- if MARKER.has_key(i):
- name, description, handler = MARKER[i]
- # print hex(i), name, description
- if handler is not None:
- handler(self, i)
- if i == 0xFFDA: # start of scan
- rawmode = self.mode
- if self.mode == "CMYK":
- rawmode = "CMYK;I" # assume adobe conventions
- self.tile = [("jpeg", (0,0) + self.size, 0, (rawmode, ""))]
- # self.__offset = self.fp.tell()
- break
- s = self.fp.read(1)
- elif i == 0 or i == 65535:
- # padded marker or junk; move on
- s = "\xff"
- else:
- raise SyntaxError("no marker found")
-
- def draft(self, mode, size):
-
- if len(self.tile) != 1:
- return
-
- d, e, o, a = self.tile[0]
- scale = 0
-
- if a[0] == "RGB" and mode in ["L", "YCbCr"]:
- self.mode = mode
- a = mode, ""
-
- if size:
- scale = max(self.size[0] / size[0], self.size[1] / size[1])
- for s in [8, 4, 2, 1]:
- if scale >= s:
- break
- e = e[0], e[1], (e[2]-e[0]+s-1)/s+e[0], (e[3]-e[1]+s-1)/s+e[1]
- self.size = ((self.size[0]+s-1)/s, (self.size[1]+s-1)/s)
- scale = s
-
- self.tile = [(d, e, o, a)]
- self.decoderconfig = (scale, 1)
-
- return self
-
- def load_djpeg(self):
-
- # ALTERNATIVE: handle JPEGs via the IJG command line utilities
-
- import tempfile, os
- file = tempfile.mktemp()
- os.system("djpeg %s >%s" % (self.filename, file))
-
- try:
- self.im = Image.core.open_ppm(file)
- finally:
- try: os.unlink(file)
- except: pass
-
- self.mode = self.im.mode
- self.size = self.im.size
-
- self.tile = []
-
- def _getexif(self):
- # Extract EXIF information. This method is highly experimental,
- # and is likely to be replaced with something better in a future
- # version.
- import TiffImagePlugin, StringIO
- def fixup(value):
- if len(value) == 1:
- return value[0]
- return value
- # The EXIF record consists of a TIFF file embedded in a JPEG
- # application marker (!).
- try:
- data = self.info["exif"]
- except KeyError:
- return None
- file = StringIO.StringIO(data[6:])
- head = file.read(8)
- exif = {}
- # process dictionary
- info = TiffImagePlugin.ImageFileDirectory(head)
- info.load(file)
- for key, value in info.items():
- exif[key] = fixup(value)
- # get exif extension
- try:
- file.seek(exif[0x8769])
- except KeyError:
- pass
- else:
- info = TiffImagePlugin.ImageFileDirectory(head)
- info.load(file)
- for key, value in info.items():
- exif[key] = fixup(value)
- # get gpsinfo extension
- try:
- file.seek(exif[0x8825])
- except KeyError:
- pass
- else:
- info = TiffImagePlugin.ImageFileDirectory(head)
- info.load(file)
- exif[0x8825] = gps = {}
- for key, value in info.items():
- gps[key] = fixup(value)
- return exif
-
-# --------------------------------------------------------------------
-# stuff to save JPEG files
-
-RAWMODE = {
- "1": "L",
- "L": "L",
- "RGB": "RGB",
- "RGBA": "RGB",
- "RGBX": "RGB",
- "CMYK": "CMYK;I", # assume adobe conventions
- "YCbCr": "YCbCr",
-}
-
-def _save(im, fp, filename):
-
- try:
- rawmode = RAWMODE[im.mode]
- except KeyError:
- raise IOError("cannot write mode %s as JPEG" % im.mode)
-
- info = im.encoderinfo
-
- dpi = info.get("dpi", (0, 0))
-
- subsampling = info.get("subsampling", -1)
- if subsampling == "4:4:4":
- subsampling = 0
- elif subsampling == "4:2:2":
- subsampling = 1
- elif subsampling == "4:1:1":
- subsampling = 2
-
- extra = ""
-
- icc_profile = info.get("icc_profile")
- if icc_profile:
- ICC_OVERHEAD_LEN = 14
- MAX_BYTES_IN_MARKER = 65533
- MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN
- markers = []
- while icc_profile:
- markers.append(icc_profile[:MAX_DATA_BYTES_IN_MARKER])
- icc_profile = icc_profile[MAX_DATA_BYTES_IN_MARKER:]
- i = 1
- for marker in markers:
- size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker))
- extra = extra + ("\xFF\xE2" + size + "ICC_PROFILE\0" + chr(i) + chr(len(markers)) + marker)
- i = i + 1
-
- # get keyword arguments
- im.encoderconfig = (
- info.get("quality", 0),
- # "progressive" is the official name, but older documentation
- # says "progression"
- # FIXME: issue a warning if the wrong form is used (post-1.1.7)
- info.has_key("progressive") or info.has_key("progression"),
- info.get("smooth", 0),
- info.has_key("optimize"),
- info.get("streamtype", 0),
- dpi[0], dpi[1],
- subsampling,
- extra,
- )
-
- ImageFile._save(im, fp, [("jpeg", (0,0)+im.size, 0, rawmode)])
-
-def _save_cjpeg(im, fp, filename):
- # ALTERNATIVE: handle JPEGs via the IJG command line utilities.
- import os
- file = im._dump()
- os.system("cjpeg %s >%s" % (file, filename))
- try: os.unlink(file)
- except: pass
-
-# -------------------------------------------------------------------q-
-# Registry stuff
-
-Image.register_open("JPEG", JpegImageFile, _accept)
-Image.register_save("JPEG", _save)
-
-Image.register_extension("JPEG", ".jfif")
-Image.register_extension("JPEG", ".jpe")
-Image.register_extension("JPEG", ".jpg")
-Image.register_extension("JPEG", ".jpeg")
-
-Image.register_mime("JPEG", "image/jpeg")
diff --git a/image_occlusion_enhanced/Imaging/PIL/PngImagePlugin.py b/image_occlusion_enhanced/Imaging/PIL/PngImagePlugin.py
deleted file mode 100644
index 0ee8589a..00000000
--- a/image_occlusion_enhanced/Imaging/PIL/PngImagePlugin.py
+++ /dev/null
@@ -1,620 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# PNG support code
-#
-# See "PNG (Portable Network Graphics) Specification, version 1.0;
-# W3C Recommendation", 1996-10-01, Thomas Boutell (ed.).
-#
-# history:
-# 1996-05-06 fl Created (couldn't resist it)
-# 1996-12-14 fl Upgraded, added read and verify support (0.2)
-# 1996-12-15 fl Separate PNG stream parser
-# 1996-12-29 fl Added write support, added getchunks
-# 1996-12-30 fl Eliminated circular references in decoder (0.3)
-# 1998-07-12 fl Read/write 16-bit images as mode I (0.4)
-# 2001-02-08 fl Added transparency support (from Zircon) (0.5)
-# 2001-04-16 fl Don't close data source in "open" method (0.6)
-# 2004-02-24 fl Don't even pretend to support interlaced files (0.7)
-# 2004-08-31 fl Do basic sanity check on chunk identifiers (0.8)
-# 2004-09-20 fl Added PngInfo chunk container
-# 2004-12-18 fl Added DPI read support (based on code by Niki Spahiev)
-# 2008-08-13 fl Added tRNS support for RGB images
-# 2009-03-06 fl Support for preserving ICC profiles (by Florian Hoech)
-# 2009-03-08 fl Added zTXT support (from Lowell Alleman)
-# 2009-03-29 fl Read interlaced PNG files (from Conrado Porto Lopes Gouvua)
-#
-# Copyright (c) 1997-2009 by Secret Labs AB
-# Copyright (c) 1996 by Fredrik Lundh
-#
-# See the README file for information on usage and redistribution.
-#
-
-__version__ = "0.9"
-
-import re, string
-
-import Image, ImageFile, ImagePalette, zlib
-
-
-def i16(c):
- return ord(c[1]) + (ord(c[0])<<8)
-def i32(c):
- return ord(c[3]) + (ord(c[2])<<8) + (ord(c[1])<<16) + (ord(c[0])<<24)
-
-is_cid = re.compile("\w\w\w\w").match
-
-
-_MAGIC = "\211PNG\r\n\032\n"
-
-
-_MODES = {
- # supported bits/color combinations, and corresponding modes/rawmodes
- (1, 0): ("1", "1"),
- (2, 0): ("L", "L;2"),
- (4, 0): ("L", "L;4"),
- (8, 0): ("L", "L"),
- (16,0): ("I", "I;16B"),
- (8, 2): ("RGB", "RGB"),
- (16,2): ("RGB", "RGB;16B"),
- (1, 3): ("P", "P;1"),
- (2, 3): ("P", "P;2"),
- (4, 3): ("P", "P;4"),
- (8, 3): ("P", "P"),
- (8, 4): ("LA", "LA"),
- (16,4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available
- (8, 6): ("RGBA", "RGBA"),
- (16,6): ("RGBA", "RGBA;16B"),
-}
-
-
-# --------------------------------------------------------------------
-# Support classes. Suitable for PNG and related formats like MNG etc.
-
-class ChunkStream:
-
- def __init__(self, fp):
-
- self.fp = fp
- self.queue = []
-
- if not hasattr(Image.core, "crc32"):
- self.crc = self.crc_skip
-
- def read(self):
- "Fetch a new chunk. Returns header information."
-
- if self.queue:
- cid, pos, len = self.queue[-1]
- del self.queue[-1]
- self.fp.seek(pos)
- else:
- s = self.fp.read(8)
- cid = s[4:]
- pos = self.fp.tell()
- len = i32(s)
-
- if not is_cid(cid):
- raise SyntaxError, "broken PNG file (chunk %s)" % repr(cid)
-
- return cid, pos, len
-
- def close(self):
- self.queue = self.crc = self.fp = None
-
- def push(self, cid, pos, len):
-
- self.queue.append((cid, pos, len))
-
- def call(self, cid, pos, len):
- "Call the appropriate chunk handler"
-
- if Image.DEBUG:
- print "STREAM", cid, pos, len
- return getattr(self, "chunk_" + cid)(pos, len)
-
- def crc(self, cid, data):
- "Read and verify checksum"
-
- crc1 = Image.core.crc32(data, Image.core.crc32(cid))
- crc2 = i16(self.fp.read(2)), i16(self.fp.read(2))
- if crc1 != crc2:
- raise SyntaxError, "broken PNG file"\
- "(bad header checksum in %s)" % cid
-
- def crc_skip(self, cid, data):
- "Read checksum. Used if the C module is not present"
-
- self.fp.read(4)
-
- def verify(self, endchunk = "IEND"):
-
- # Simple approach; just calculate checksum for all remaining
- # blocks. Must be called directly after open.
-
- cids = []
-
- while 1:
- cid, pos, len = self.read()
- if cid == endchunk:
- break
- self.crc(cid, ImageFile._safe_read(self.fp, len))
- cids.append(cid)
-
- return cids
-
-
-# --------------------------------------------------------------------
-# PNG chunk container (for use with save(pnginfo=))
-
-class PngInfo:
-
- def __init__(self):
- self.chunks = []
-
- def add(self, cid, data):
- self.chunks.append((cid, data))
-
- def add_text(self, key, value, zip=0):
- if zip:
- import zlib
- self.add("zTXt", key + "\0\0" + zlib.compress(value))
- else:
- self.add("tEXt", key + "\0" + value)
-
-# --------------------------------------------------------------------
-# PNG image stream (IHDR/IEND)
-
-class PngStream(ChunkStream):
-
- def __init__(self, fp):
-
- ChunkStream.__init__(self, fp)
-
- # local copies of Image attributes
- self.im_info = {}
- self.im_text = {}
- self.im_size = (0,0)
- self.im_mode = None
- self.im_tile = None
- self.im_palette = None
-
- def chunk_iCCP(self, pos, len):
-
- # ICC profile
- s = ImageFile._safe_read(self.fp, len)
- # according to PNG spec, the iCCP chunk contains:
- # Profile name 1-79 bytes (character string)
- # Null separator 1 byte (null character)
- # Compression method 1 byte (0)
- # Compressed profile n bytes (zlib with deflate compression)
- i = string.find(s, chr(0))
- if Image.DEBUG:
- print "iCCP profile name", s[:i]
- print "Compression method", ord(s[i])
- comp_method = ord(s[i])
- if comp_method != 0:
- raise SyntaxError("Unknown compression method %s in iCCP chunk" % comp_method)
- try:
- icc_profile = zlib.decompress(s[i+2:])
- except zlib.error:
- icc_profile = None # FIXME
- self.im_info["icc_profile"] = icc_profile
- return s
-
- def chunk_IHDR(self, pos, len):
-
- # image header
- s = ImageFile._safe_read(self.fp, len)
- self.im_size = i32(s), i32(s[4:])
- try:
- self.im_mode, self.im_rawmode = _MODES[(ord(s[8]), ord(s[9]))]
- except:
- pass
- if ord(s[12]):
- self.im_info["interlace"] = 1
- if ord(s[11]):
- raise SyntaxError, "unknown filter category"
- return s
-
- def chunk_IDAT(self, pos, len):
-
- # image data
- self.im_tile = [("zip", (0,0)+self.im_size, pos, self.im_rawmode)]
- self.im_idat = len
- raise EOFError
-
- def chunk_IEND(self, pos, len):
-
- # end of PNG image
- raise EOFError
-
- def chunk_PLTE(self, pos, len):
-
- # palette
- s = ImageFile._safe_read(self.fp, len)
- if self.im_mode == "P":
- self.im_palette = "RGB", s
- return s
-
- def chunk_tRNS(self, pos, len):
-
- # transparency
- s = ImageFile._safe_read(self.fp, len)
- if self.im_mode == "P":
- i = string.find(s, chr(0))
- if i >= 0:
- self.im_info["transparency"] = i
- elif self.im_mode == "L":
- self.im_info["transparency"] = i16(s)
- elif self.im_mode == "RGB":
- self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:])
- return s
-
- def chunk_gAMA(self, pos, len):
-
- # gamma setting
- s = ImageFile._safe_read(self.fp, len)
- self.im_info["gamma"] = i32(s) / 100000.0
- return s
-
- def chunk_pHYs(self, pos, len):
-
- # pixels per unit
- s = ImageFile._safe_read(self.fp, len)
- px, py = i32(s), i32(s[4:])
- unit = ord(s[8])
- if unit == 1: # meter
- dpi = int(px * 0.0254 + 0.5), int(py * 0.0254 + 0.5)
- self.im_info["dpi"] = dpi
- elif unit == 0:
- self.im_info["aspect"] = px, py
- return s
-
- def chunk_tEXt(self, pos, len):
-
- # text
- s = ImageFile._safe_read(self.fp, len)
- try:
- k, v = string.split(s, "\0", 1)
- except ValueError:
- k = s; v = "" # fallback for broken tEXt tags
- if k:
- self.im_info[k] = self.im_text[k] = v
- return s
-
- def chunk_zTXt(self, pos, len):
-
- # compressed text
- s = ImageFile._safe_read(self.fp, len)
- k, v = string.split(s, "\0", 1)
- comp_method = ord(v[0])
- if comp_method != 0:
- raise SyntaxError("Unknown compression method %s in zTXt chunk" % comp_method)
- import zlib
- self.im_info[k] = self.im_text[k] = zlib.decompress(v[1:])
- return s
-
-# --------------------------------------------------------------------
-# PNG reader
-
-def _accept(prefix):
- return prefix[:8] == _MAGIC
-
-##
-# Image plugin for PNG images.
-
-class PngImageFile(ImageFile.ImageFile):
-
- format = "PNG"
- format_description = "Portable network graphics"
-
- def _open(self):
-
- if self.fp.read(8) != _MAGIC:
- raise SyntaxError, "not a PNG file"
-
- #
- # Parse headers up to the first IDAT chunk
-
- self.png = PngStream(self.fp)
-
- while 1:
-
- #
- # get next chunk
-
- cid, pos, len = self.png.read()
-
- try:
- s = self.png.call(cid, pos, len)
- except EOFError:
- break
- except AttributeError:
- if Image.DEBUG:
- print cid, pos, len, "(unknown)"
- s = ImageFile._safe_read(self.fp, len)
-
- self.png.crc(cid, s)
-
- #
- # Copy relevant attributes from the PngStream. An alternative
- # would be to let the PngStream class modify these attributes
- # directly, but that introduces circular references which are
- # difficult to break if things go wrong in the decoder...
- # (believe me, I've tried ;-)
-
- self.mode = self.png.im_mode
- self.size = self.png.im_size
- self.info = self.png.im_info
- self.text = self.png.im_text # experimental
- self.tile = self.png.im_tile
-
- if self.png.im_palette:
- rawmode, data = self.png.im_palette
- self.palette = ImagePalette.raw(rawmode, data)
-
- self.__idat = len # used by load_read()
-
-
- def verify(self):
- "Verify PNG file"
-
- if self.fp is None:
- raise RuntimeError("verify must be called directly after open")
-
- # back up to beginning of IDAT block
- self.fp.seek(self.tile[0][2] - 8)
-
- self.png.verify()
- self.png.close()
-
- self.fp = None
-
- def load_prepare(self):
- "internal: prepare to read PNG file"
-
- if self.info.get("interlace"):
- self.decoderconfig = self.decoderconfig + (1,)
-
- ImageFile.ImageFile.load_prepare(self)
-
- def load_read(self, bytes):
- "internal: read more image data"
-
- while self.__idat == 0:
- # end of chunk, skip forward to next one
-
- self.fp.read(4) # CRC
-
- cid, pos, len = self.png.read()
-
- if cid not in ["IDAT", "DDAT"]:
- self.png.push(cid, pos, len)
- return ""
-
- self.__idat = len # empty chunks are allowed
-
- # read more data from this chunk
- if bytes <= 0:
- bytes = self.__idat
- else:
- bytes = min(bytes, self.__idat)
-
- self.__idat = self.__idat - bytes
-
- return self.fp.read(bytes)
-
-
- def load_end(self):
- "internal: finished reading image data"
-
- self.png.close()
- self.png = None
-
-
-# --------------------------------------------------------------------
-# PNG writer
-
-def o16(i):
- return chr(i>>8&255) + chr(i&255)
-
-def o32(i):
- return chr(i>>24&255) + chr(i>>16&255) + chr(i>>8&255) + chr(i&255)
-
-_OUTMODES = {
- # supported PIL modes, and corresponding rawmodes/bits/color combinations
- "1": ("1", chr(1)+chr(0)),
- "L;1": ("L;1", chr(1)+chr(0)),
- "L;2": ("L;2", chr(2)+chr(0)),
- "L;4": ("L;4", chr(4)+chr(0)),
- "L": ("L", chr(8)+chr(0)),
- "LA": ("LA", chr(8)+chr(4)),
- "I": ("I;16B", chr(16)+chr(0)),
- "P;1": ("P;1", chr(1)+chr(3)),
- "P;2": ("P;2", chr(2)+chr(3)),
- "P;4": ("P;4", chr(4)+chr(3)),
- "P": ("P", chr(8)+chr(3)),
- "RGB": ("RGB", chr(8)+chr(2)),
- "RGBA":("RGBA", chr(8)+chr(6)),
-}
-
-def putchunk(fp, cid, *data):
- "Write a PNG chunk (including CRC field)"
-
- data = string.join(data, "")
-
- fp.write(o32(len(data)) + cid)
- fp.write(data)
- hi, lo = Image.core.crc32(data, Image.core.crc32(cid))
- fp.write(o16(hi) + o16(lo))
-
-class _idat:
- # wrap output from the encoder in IDAT chunks
-
- def __init__(self, fp, chunk):
- self.fp = fp
- self.chunk = chunk
- def write(self, data):
- self.chunk(self.fp, "IDAT", data)
-
-def _save(im, fp, filename, chunk=putchunk, check=0):
- # save an image to disk (called by the save method)
-
- mode = im.mode
-
- if mode == "P":
-
- #
- # attempt to minimize storage requirements for palette images
-
- if im.encoderinfo.has_key("bits"):
-
- # number of bits specified by user
- n = 1 << im.encoderinfo["bits"]
-
- else:
-
- # check palette contents
- n = 256 # FIXME
-
- if n <= 2:
- bits = 1
- elif n <= 4:
- bits = 2
- elif n <= 16:
- bits = 4
- else:
- bits = 8
-
- if bits != 8:
- mode = "%s;%d" % (mode, bits)
-
- # encoder options
- if im.encoderinfo.has_key("dictionary"):
- dictionary = im.encoderinfo["dictionary"]
- else:
- dictionary = ""
-
- im.encoderconfig = (im.encoderinfo.has_key("optimize"), dictionary)
-
- # get the corresponding PNG mode
- try:
- rawmode, mode = _OUTMODES[mode]
- except KeyError:
- raise IOError, "cannot write mode %s as PNG" % mode
-
- if check:
- return check
-
- #
- # write minimal PNG file
-
- fp.write(_MAGIC)
-
- chunk(fp, "IHDR",
- o32(im.size[0]), o32(im.size[1]), # 0: size
- mode, # 8: depth/type
- chr(0), # 10: compression
- chr(0), # 11: filter category
- chr(0)) # 12: interlace flag
-
- if im.mode == "P":
- chunk(fp, "PLTE", im.im.getpalette("RGB"))
-
- if im.encoderinfo.has_key("transparency"):
- if im.mode == "P":
- transparency = max(0, min(255, im.encoderinfo["transparency"]))
- chunk(fp, "tRNS", chr(255) * transparency + chr(0))
- elif im.mode == "L":
- transparency = max(0, min(65535, im.encoderinfo["transparency"]))
- chunk(fp, "tRNS", o16(transparency))
- elif im.mode == "RGB":
- red, green, blue = im.encoderinfo["transparency"]
- chunk(fp, "tRNS", o16(red) + o16(green) + o16(blue))
- else:
- raise IOError("cannot use transparency for this mode")
-
- if 0:
- # FIXME: to be supported some day
- chunk(fp, "gAMA", o32(int(gamma * 100000.0)))
-
- dpi = im.encoderinfo.get("dpi")
- if dpi:
- chunk(fp, "pHYs",
- o32(int(dpi[0] / 0.0254 + 0.5)),
- o32(int(dpi[1] / 0.0254 + 0.5)),
- chr(1))
-
- info = im.encoderinfo.get("pnginfo")
- if info:
- for cid, data in info.chunks:
- chunk(fp, cid, data)
-
- # ICC profile writing support -- 2008-06-06 Florian Hoech
- if im.info.has_key("icc_profile"):
- # ICC profile
- # according to PNG spec, the iCCP chunk contains:
- # Profile name 1-79 bytes (character string)
- # Null separator 1 byte (null character)
- # Compression method 1 byte (0)
- # Compressed profile n bytes (zlib with deflate compression)
- try:
- import ICCProfile
- p = ICCProfile.ICCProfile(im.info["icc_profile"])
- name = p.tags.desc.get("ASCII", p.tags.desc.get("Unicode", p.tags.desc.get("Macintosh", p.tags.desc.get("en", {}).get("US", "ICC Profile")))).encode("latin1", "replace")[:79]
- except ImportError:
- name = "ICC Profile"
- data = name + "\0\0" + zlib.compress(im.info["icc_profile"])
- chunk(fp, "iCCP", data)
-
- ImageFile._save(im, _idat(fp, chunk), [("zip", (0,0)+im.size, 0, rawmode)])
-
- chunk(fp, "IEND", "")
-
- try:
- fp.flush()
- except:
- pass
-
-
-# --------------------------------------------------------------------
-# PNG chunk converter
-
-def getchunks(im, **params):
- """Return a list of PNG chunks representing this image."""
-
- class collector:
- data = []
- def write(self, data):
- pass
- def append(self, chunk):
- self.data.append(chunk)
-
- def append(fp, cid, *data):
- data = string.join(data, "")
- hi, lo = Image.core.crc32(data, Image.core.crc32(cid))
- crc = o16(hi) + o16(lo)
- fp.append((cid, data, crc))
-
- fp = collector()
-
- try:
- im.encoderinfo = params
- _save(im, fp, None, append)
- finally:
- del im.encoderinfo
-
- return fp.data
-
-
-# --------------------------------------------------------------------
-# Registry
-
-Image.register_open("PNG", PngImageFile, _accept)
-Image.register_save("PNG", _save)
-
-Image.register_extension("PNG", ".png")
-
-Image.register_mime("PNG", "image/png")
diff --git a/image_occlusion_enhanced/Imaging/PIL/PpmImagePlugin.py b/image_occlusion_enhanced/Imaging/PIL/PpmImagePlugin.py
deleted file mode 100644
index e86146c1..00000000
--- a/image_occlusion_enhanced/Imaging/PIL/PpmImagePlugin.py
+++ /dev/null
@@ -1,131 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# PPM support for PIL
-#
-# History:
-# 96-03-24 fl Created
-# 98-03-06 fl Write RGBA images (as RGB, that is)
-#
-# Copyright (c) Secret Labs AB 1997-98.
-# Copyright (c) Fredrik Lundh 1996.
-#
-# See the README file for information on usage and redistribution.
-#
-
-
-__version__ = "0.2"
-
-import string
-
-import Image, ImageFile
-
-#
-# --------------------------------------------------------------------
-
-MODES = {
- # standard
- "P4": "1",
- "P5": "L",
- "P6": "RGB",
- # extensions
- "P0CMYK": "CMYK",
- # PIL extensions (for test purposes only)
- "PyP": "P",
- "PyRGBA": "RGBA",
- "PyCMYK": "CMYK"
-}
-
-def _accept(prefix):
- return prefix[0] == "P" and prefix[1] in "0456y"
-
-##
-# Image plugin for PBM, PGM, and PPM images.
-
-class PpmImageFile(ImageFile.ImageFile):
-
- format = "PPM"
- format_description = "Pbmplus image"
-
- def _token(self, s = ""):
- while 1: # read until next whitespace
- c = self.fp.read(1)
- if not c or c in string.whitespace:
- break
- s = s + c
- return s
-
- def _open(self):
-
- # check magic
- s = self.fp.read(1)
- if s != "P":
- raise SyntaxError, "not a PPM file"
- mode = MODES[self._token(s)]
-
- if mode == "1":
- self.mode = "1"
- rawmode = "1;I"
- else:
- self.mode = rawmode = mode
-
- for ix in range(3):
- while 1:
- while 1:
- s = self.fp.read(1)
- if s not in string.whitespace:
- break
- if s != "#":
- break
- s = self.fp.readline()
- s = int(self._token(s))
- if ix == 0:
- xsize = s
- elif ix == 1:
- ysize = s
- if mode == "1":
- break
-
- self.size = xsize, ysize
- self.tile = [("raw",
- (0, 0, xsize, ysize),
- self.fp.tell(),
- (rawmode, 0, 1))]
-
- # ALTERNATIVE: load via builtin debug function
- # self.im = Image.core.open_ppm(self.filename)
- # self.mode = self.im.mode
- # self.size = self.im.size
-
-#
-# --------------------------------------------------------------------
-
-def _save(im, fp, filename):
- if im.mode == "1":
- rawmode, head = "1;I", "P4"
- elif im.mode == "L":
- rawmode, head = "L", "P5"
- elif im.mode == "RGB":
- rawmode, head = "RGB", "P6"
- elif im.mode == "RGBA":
- rawmode, head = "RGB", "P6"
- else:
- raise IOError, "cannot write mode %s as PPM" % im.mode
- fp.write(head + "\n%d %d\n" % im.size)
- if head != "P4":
- fp.write("255\n")
- ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, 1))])
-
- # ALTERNATIVE: save via builtin debug function
- # im._dump(filename)
-
-#
-# --------------------------------------------------------------------
-
-Image.register_open("PPM", PpmImageFile, _accept)
-Image.register_save("PPM", _save)
-
-Image.register_extension("PPM", ".pbm")
-Image.register_extension("PPM", ".pgm")
-Image.register_extension("PPM", ".ppm")
diff --git a/image_occlusion_enhanced/Imaging/PIL/__init__.py b/image_occlusion_enhanced/Imaging/PIL/__init__.py
deleted file mode 100644
index ed54d26f..00000000
--- a/image_occlusion_enhanced/Imaging/PIL/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# package placeholder
-#
-# Copyright (c) 1999 by Secret Labs AB.
-#
-# See the README file for information on usage and redistribution.
-#
-
-# ;-)
diff --git a/image_occlusion_enhanced/Imaging/README b/image_occlusion_enhanced/Imaging/README
deleted file mode 100644
index 458975b3..00000000
--- a/image_occlusion_enhanced/Imaging/README
+++ /dev/null
@@ -1,300 +0,0 @@
-The Python Imaging Library
-$Id$
-
-Release 1.1.7 (November 15, 2009)
-
-====================================================================
-The Python Imaging Library 1.1.7
-====================================================================
-
-Contents
---------
-
-+ Introduction
-+ Support Options
- - Commercial support
- - Free support
-+ Software License
-+ Build instructions (all platforms)
- - Additional notes for Mac OS X
- - Additional notes for Windows
-
---------------------------------------------------------------------
-Introduction
---------------------------------------------------------------------
-
-The Python Imaging Library (PIL) adds image processing capabilities
-to your Python environment. This library provides extensive file
-format support, an efficient internal representation, and powerful
-image processing capabilities.
-
-This source kit has been built and tested with Python 2.0 and newer,
-on Windows, Mac OS X, and major Unix platforms. Large parts of the
-library also work on 1.5.2 and 1.6.
-
-The main distribution site for this software is:
-
- http://www.pythonware.com/products/pil/
-
-That site also contains information about free and commercial support
-options, PIL add-ons, answers to frequently asked questions, and more.
-
-
-Development versions (alphas, betas) are available here:
-
- http://effbot.org/downloads/
-
-
-The PIL handbook is not included in this distribution; to get the
-latest version, check:
-
- http://www.pythonware.com/library/
- http://effbot.org/books/imagingbook/ (drafts)
-
-
-For installation and licensing details, see below.
-
-
---------------------------------------------------------------------
-Support Options
---------------------------------------------------------------------
-
-+ Commercial Support
-
-Secret Labs (PythonWare) offers support contracts for companies using
-the Python Imaging Library in commercial applications, and in mission-
-critical environments. The support contract includes technical support,
-bug fixes, extensions to the PIL library, sample applications, and more.
-
-For the full story, check:
-
- http://www.pythonware.com/products/pil/support.htm
-
-
-+ Free Support
-
-For support and general questions on the Python Imaging Library, send
-e-mail to the Image SIG mailing list:
-
- image-sig@python.org
-
-You can join the Image SIG by sending a mail to:
-
- image-sig-request@python.org
-
-Put "subscribe" in the message body to automatically subscribe to the
-list, or "help" to get additional information. Alternatively, you can
-send your questions to the Python mailing list, python-list@python.org,
-or post them to the newsgroup comp.lang.python. DO NOT SEND SUPPORT
-QUESTIONS TO PYTHONWARE ADDRESSES.
-
-
---------------------------------------------------------------------
-Software License
---------------------------------------------------------------------
-
-The Python Imaging Library is
-
-Copyright (c) 1997-2009 by Secret Labs AB
-Copyright (c) 1995-2009 by Fredrik Lundh
-
-By obtaining, using, and/or copying this software and/or its
-associated documentation, you agree that you have read, understood,
-and will comply with the following terms and conditions:
-
-Permission to use, copy, modify, and distribute this software and its
-associated documentation for any purpose and without fee is hereby
-granted, provided that the above copyright notice appears in all
-copies, and that both that copyright notice and this permission notice
-appear in supporting documentation, and that the name of Secret Labs
-AB or the author not be used in advertising or publicity pertaining to
-distribution of the software without specific, written prior
-permission.
-
-SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO
-THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR
-ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
-OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-
---------------------------------------------------------------------
-Build instructions (all platforms)
---------------------------------------------------------------------
-
-For a list of changes in this release, see the CHANGES document.
-
-0. If you're in a hurry, try this:
-
- $ tar xvfz Imaging-1.1.7.tar.gz
- $ cd Imaging-1.1.7
- $ python setup.py install
-
- If you prefer to know what you're doing, read on.
-
-
-1. Prerequisites.
-
- If you need any of the features described below, make sure you
- have the necessary libraries before building PIL.
-
- feature library
- -----------------------------------------------------------------
- JPEG support libjpeg (6a or 6b)
-
- http://www.ijg.org
- http://www.ijg.org/files/jpegsrc.v6b.tar.gz
- ftp://ftp.uu.net/graphics/jpeg/
-
- PNG support zlib (1.2.3 or later is recommended)
-
- http://www.gzip.org/zlib/
-
- OpenType/TrueType freetype2 (2.3.9 or later is recommended)
- support
- http://www.freetype.org
- http://freetype.sourceforge.net
-
- CMS support littleCMS (1.1.5 or later is recommended)
- support
- http://www.littlecms.com/
-
- If you have a recent Linux version, the libraries provided with the
- operating system usually work just fine. If some library is
- missing, installing a prebuilt version (jpeg-devel, zlib-devel,
- etc) is usually easier than building from source. For example, for
- Ubuntu 9.10 (karmic), you can install the following libraries:
-
- sudo apt-get install libjpeg62-dev
- sudo apt-get install zlib1g-dev
- sudo apt-get install libfreetype6-dev
- sudo apt-get install liblcms1-dev
-
- If you're using Mac OS X, you can use the 'fink' tool to install
- missing libraries (also see the Mac OS X section below).
-
- Similar tools are available for many other platforms.
-
-
-2. To build under Python 1.5.2, you need to install the stand-alone
- version of the distutils library:
-
- http://www.python.org/sigs/distutils-sig/download.html
-
- You can fetch distutils 1.0.2 from the Python source repository:
-
- svn export http://svn.python.org/projects/python/tags/Distutils-1_0_2/Lib/distutils/
-
- For newer releases, the distutils library is included in the
- Python standard library.
-
- NOTE: Version 1.1.7 is not fully compatible with 1.5.2. Some
- more recent additions to the library may not work, but the core
- functionality is available.
-
-
-3. If you didn't build Python from sources, make sure you have
- Python's build support files on your machine. If you've down-
- loaded a prebuilt package (e.g. a Linux RPM), you probably
- need additional developer packages. Look for packages named
- "python-dev", "python-devel", or similar. For example, for
- Ubuntu 9.10 (karmic), use the following command:
-
- sudo apt-get install python-dev
-
-
-4. When you have everything you need, unpack the PIL distribution
- (the file Imaging-1.1.7.tar.gz) in a suitable work directory:
-
- $ cd MyExtensions # example
- $ gunzip Imaging-1.1.7.tar.gz
- $ tar xvf Imaging-1.1.7.tar
-
-
-5. Build the library. We recommend that you do an in-place build,
- and run the self test before installing.
-
- $ cd Imaging-1.1.7
- $ python setup.py build_ext -i
- $ python selftest.py
-
- During the build process, the setup.py will display a summary
- report that lists what external components it found. The self-
- test will display a similar report, with what external components
- the tests found in the actual build files:
-
- ----------------------------------------------------------------
- PIL 1.1.7 SETUP SUMMARY
- ----------------------------------------------------------------
- *** TKINTER support not available (Tcl/Tk 8.5 libraries needed)
- --- JPEG support available
- --- ZLIB (PNG/ZIP) support available
- --- FREETYPE support available
- ----------------------------------------------------------------
-
- Make sure that the optional components you need are included.
-
- If the build script won't find a given component, you can edit the
- setup.py file and set the appropriate ROOT variable. For details,
- see instructions in the file.
-
- If the build script finds the component, but the tests cannot
- identify it, try rebuilding *all* modules:
-
- $ python setup.py clean
- $ python setup.py build_ext -i
-
-
-6. If the setup.py and selftest.py commands finish without any
- errors, you're ready to install the library:
-
- $ python setup.py install
-
- (depending on how Python has been installed on your machine,
- you might have to log in as a superuser to run the 'install'
- command, or use the 'sudo' command to run 'install'.)
-
-
---------------------------------------------------------------------
-Additional notes for Mac OS X
---------------------------------------------------------------------
-
-On Mac OS X you will usually install additional software such as
-libjpeg or freetype with the "fink" tool, and then it ends up in
-"/sw". If you have installed the libraries elsewhere, you may have
-to tweak the "setup.py" file before building.
-
-
---------------------------------------------------------------------
-Additional notes for Windows
---------------------------------------------------------------------
-
-On Windows, you need to tweak the ROOT settings in the "setup.py"
-file, to make it find the external libraries. See comments in the
-file for details.
-
-Make sure to build PIL and the external libraries with the same
-runtime linking options as was used for the Python interpreter
-(usually /MD, under Visual Studio).
-
-
-Note that most Python distributions for Windows include libraries
-compiled for Microsoft Visual Studio. You can get the free Express
-edition of Visual Studio from:
-
- http://www.microsoft.com/Express/
-
-To build extensions using other tool chains, see the "Using
-non-Microsoft compilers on Windows" section in the distutils handbook:
-
- http://www.python.org/doc/current/inst/non-ms-compilers.html
-
-For additional information on how to build extensions using the
-popular MinGW compiler, see:
-
- http://mingw.org (compiler)
- http://sebsauvage.net/python/mingw.html (build instructions)
- http://sourceforge.net/projects/gnuwin32 (prebuilt libraries)
-
diff --git a/image_occlusion_enhanced/LICENSE b/image_occlusion_enhanced/LICENSE
deleted file mode 100644
index dba13ed2..00000000
--- a/image_occlusion_enhanced/LICENSE
+++ /dev/null
@@ -1,661 +0,0 @@
- GNU AFFERO GENERAL PUBLIC LICENSE
- Version 3, 19 November 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc. Basic Instructions Drawing Custom Labels Grouping Shapes Grouped shapes will form a single card. More Information For more information please refer to the following resources: Official Video Tutorial Series Copyright © 2012-2015 \
- Tiago Barroso Copyright © 2013 \
- Steve AW Copyright © 2016-2017 \
- Aristotelis P. Image Occlusion Enhanced is licensed under the GNU AGPLv3. Third-party open-source software shipped with Image Occlusion Enhanced: SVG Edit 2.6. \
- Copyright (c) 2009-2012 SVG-edit authors. Licensed under the MIT license Python Imaging Library \
- (PIL) 1.1.7. Copyright (c) 1997-2011 by Secret Labs AB, Copyright (c) 1995-2011 by Fredrik \
- Lundh. Licensed under the \
- PIL license imagesize.py v0.7.1. \
- Copyright (c) 2016 Yoshiki Shibukawa. Licensed under the MIT license. Sorry, but your browser does not support SVG. Below is a list of alternate browsers and versions that support SVG and SVG-edit (from caniuse.com). Try the latest version of Firefox, Google Chrome, Safari, Opera or Internet Explorer. If you are unable to install one of these and must use an old version of Internet Explorer, you can install the Google Chrome Frame plugin.
-# out = image1 * (1.0 - alpha) + image2 * alpha
-#
-#
-# @param im1 The first image.
-# @param im2 The second image. Must have the same mode and size as
-# the first image.
-# @param alpha The interpolation alpha factor. If alpha is 0.0, a
-# copy of the first image is returned. If alpha is 1.0, a copy of
-# the second image is returned. There are no restrictions on the
-# alpha value. If necessary, the result is clipped to fit into
-# the allowed output range.
-# @return An Image object.
-
-def blend(im1, im2, alpha):
- "Interpolate between images."
-
- im1.load()
- im2.load()
- return im1._new(core.blend(im1.im, im2.im, alpha))
-
-##
-# Creates a new image by interpolating between two input images,
-# using the mask as alpha.
-#
-# @param image1 The first image.
-# @param image2 The second image. Must have the same mode and
-# size as the first image.
-# @param mask A mask image. This image can can have mode
-# "1", "L", or "RGBA", and must have the same size as the
-# other two images.
-
-def composite(image1, image2, mask):
- "Create composite image by blending images using a transparency mask"
-
- image = image2.copy()
- image.paste(image1, None, mask)
- return image
-
-##
-# Applies the function (which should take one argument) to each pixel
-# in the given image. If the image has more than one band, the same
-# function is applied to each band. Note that the function is
-# evaluated once for each possible pixel value, so you cannot use
-# random components or other generators.
-#
-# @def eval(image, function)
-# @param image The input image.
-# @param function A function object, taking one integer argument.
-# @return An Image object.
-
-def eval(image, *args):
- "Evaluate image expression"
-
- return image.point(args[0])
-
-##
-# Creates a new image from a number of single-band images.
-#
-# @param mode The mode to use for the output image.
-# @param bands A sequence containing one single-band image for
-# each band in the output image. All bands must have the
-# same size.
-# @return An Image object.
-
-def merge(mode, bands):
- "Merge a set of single band images into a new multiband image."
-
- if getmodebands(mode) != len(bands) or "*" in mode:
- raise ValueError("wrong number of bands")
- for im in bands[1:]:
- if im.mode != getmodetype(mode):
- raise ValueError("mode mismatch")
- if im.size != bands[0].size:
- raise ValueError("size mismatch")
- im = core.new(mode, bands[0].size)
- for i in range(getmodebands(mode)):
- bands[i].load()
- im.putband(bands[i].im, i)
- return bands[0]._new(im)
-
-# --------------------------------------------------------------------
-# Plugin registry
-
-##
-# Register an image file plugin. This function should not be used
-# in application code.
-#
-# @param id An image format identifier.
-# @param factory An image file factory method.
-# @param accept An optional function that can be used to quickly
-# reject images having another format.
-
-def register_open(id, factory, accept=None):
- id = string.upper(id)
- ID.append(id)
- OPEN[id] = factory, accept
-
-##
-# Registers an image MIME type. This function should not be used
-# in application code.
-#
-# @param id An image format identifier.
-# @param mimetype The image MIME type for this format.
-
-def register_mime(id, mimetype):
- MIME[string.upper(id)] = mimetype
-
-##
-# Registers an image save function. This function should not be
-# used in application code.
-#
-# @param id An image format identifier.
-# @param driver A function to save images in this format.
-
-def register_save(id, driver):
- SAVE[string.upper(id)] = driver
-
-##
-# Registers an image extension. This function should not be
-# used in application code.
-#
-# @param id An image format identifier.
-# @param extension An extension used for this format.
-
-def register_extension(id, extension):
- EXTENSION[string.lower(extension)] = string.upper(id)
-
-
-# --------------------------------------------------------------------
-# Simple display support. User code may override this.
-
-def _show(image, **options):
- # override me, as necessary
- apply(_showxv, (image,), options)
-
-def _showxv(image, title=None, **options):
- import ImageShow
- apply(ImageShow.show, (image, title), options)
diff --git a/image_occlusion_enhanced/Imaging/PIL/ImageColor.py b/image_occlusion_enhanced/Imaging/PIL/ImageColor.py
deleted file mode 100644
index c3cca46d..00000000
--- a/image_occlusion_enhanced/Imaging/PIL/ImageColor.py
+++ /dev/null
@@ -1,263 +0,0 @@
-#
-# The Python Imaging Library
-# $Id$
-#
-# map CSS3-style colour description strings to RGB
-#
-# History:
-# 2002-10-24 fl Added support for CSS-style color strings
-# 2002-12-15 fl Added RGBA support
-# 2004-03-27 fl Fixed remaining int() problems for Python 1.5.2
-# 2004-07-19 fl Fixed gray/grey spelling issues
-# 2009-03-05 fl Fixed rounding error in grayscale calculation
-#
-# Copyright (c) 2002-2004 by Secret Labs AB
-# Copyright (c) 2002-2004 by Fredrik Lundh
-#
-# See the README file for information on usage and redistribution.
-#
-
-import Image
-import re, string
-
-try:
- x = int("a", 16)
-except TypeError:
- # python 1.5.2 doesn't support int(x,b)
- str2int = string.atoi
-else:
- str2int = int
-
-##
-# Convert color string to RGB tuple.
-#
-# @param color A CSS3-style colour string.
-# @return An RGB-tuple.
-# @exception ValueError If the color string could not be interpreted
-# as an RGB value.
-
-def getrgb(color):
- # FIXME: add RGBA support
- try:
- rgb = colormap[color]
- except KeyError:
- try:
- # fall back on case-insensitive lookup
- rgb = colormap[string.lower(color)]
- except KeyError:
- rgb = None
- # found color in cache
- if rgb:
- if isinstance(rgb, type(())):
- return rgb
- colormap[color] = rgb = getrgb(rgb)
- return rgb
- # check for known string formats
- m = re.match("#\w\w\w$", color)
- if m:
- return (
- str2int(color[1]*2, 16),
- str2int(color[2]*2, 16),
- str2int(color[3]*2, 16)
- )
- m = re.match("#\w\w\w\w\w\w$", color)
- if m:
- return (
- str2int(color[1:3], 16),
- str2int(color[3:5], 16),
- str2int(color[5:7], 16)
- )
- m = re.match("rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
- if m:
- return (
- str2int(m.group(1)),
- str2int(m.group(2)),
- str2int(m.group(3))
- )
- m = re.match("rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
- if m:
- return (
- int((str2int(m.group(1)) * 255) / 100.0 + 0.5),
- int((str2int(m.group(2)) * 255) / 100.0 + 0.5),
- int((str2int(m.group(3)) * 255) / 100.0 + 0.5)
- )
- m = re.match("hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
- if m:
- from colorsys import hls_to_rgb
- rgb = hls_to_rgb(
- float(m.group(1)) / 360.0,
- float(m.group(3)) / 100.0,
- float(m.group(2)) / 100.0,
- )
- return (
- int(rgb[0] * 255 + 0.5),
- int(rgb[1] * 255 + 0.5),
- int(rgb[2] * 255 + 0.5)
- )
- raise ValueError("unknown color specifier: %r" % color)
-
-def getcolor(color, mode):
- # same as getrgb, but converts the result to the given mode
- color = getrgb(color)
- if mode == "RGB":
- return color
- if mode == "RGBA":
- r, g, b = color
- return r, g, b, 255
- if Image.getmodebase(mode) == "L":
- r, g, b = color
- return (r*299 + g*587 + b*114)/1000
- return color
-
-colormap = {
- # X11 colour table (from "CSS3 module: Color working draft"), with
- # gray/grey spelling issues fixed. This is a superset of HTML 4.0
- # colour names used in CSS 1.
- "aliceblue": "#f0f8ff",
- "antiquewhite": "#faebd7",
- "aqua": "#00ffff",
- "aquamarine": "#7fffd4",
- "azure": "#f0ffff",
- "beige": "#f5f5dc",
- "bisque": "#ffe4c4",
- "black": "#000000",
- "blanchedalmond": "#ffebcd",
- "blue": "#0000ff",
- "blueviolet": "#8a2be2",
- "brown": "#a52a2a",
- "burlywood": "#deb887",
- "cadetblue": "#5f9ea0",
- "chartreuse": "#7fff00",
- "chocolate": "#d2691e",
- "coral": "#ff7f50",
- "cornflowerblue": "#6495ed",
- "cornsilk": "#fff8dc",
- "crimson": "#dc143c",
- "cyan": "#00ffff",
- "darkblue": "#00008b",
- "darkcyan": "#008b8b",
- "darkgoldenrod": "#b8860b",
- "darkgray": "#a9a9a9",
- "darkgrey": "#a9a9a9",
- "darkgreen": "#006400",
- "darkkhaki": "#bdb76b",
- "darkmagenta": "#8b008b",
- "darkolivegreen": "#556b2f",
- "darkorange": "#ff8c00",
- "darkorchid": "#9932cc",
- "darkred": "#8b0000",
- "darksalmon": "#e9967a",
- "darkseagreen": "#8fbc8f",
- "darkslateblue": "#483d8b",
- "darkslategray": "#2f4f4f",
- "darkslategrey": "#2f4f4f",
- "darkturquoise": "#00ced1",
- "darkviolet": "#9400d3",
- "deeppink": "#ff1493",
- "deepskyblue": "#00bfff",
- "dimgray": "#696969",
- "dimgrey": "#696969",
- "dodgerblue": "#1e90ff",
- "firebrick": "#b22222",
- "floralwhite": "#fffaf0",
- "forestgreen": "#228b22",
- "fuchsia": "#ff00ff",
- "gainsboro": "#dcdcdc",
- "ghostwhite": "#f8f8ff",
- "gold": "#ffd700",
- "goldenrod": "#daa520",
- "gray": "#808080",
- "grey": "#808080",
- "green": "#008000",
- "greenyellow": "#adff2f",
- "honeydew": "#f0fff0",
- "hotpink": "#ff69b4",
- "indianred": "#cd5c5c",
- "indigo": "#4b0082",
- "ivory": "#fffff0",
- "khaki": "#f0e68c",
- "lavender": "#e6e6fa",
- "lavenderblush": "#fff0f5",
- "lawngreen": "#7cfc00",
- "lemonchiffon": "#fffacd",
- "lightblue": "#add8e6",
- "lightcoral": "#f08080",
- "lightcyan": "#e0ffff",
- "lightgoldenrodyellow": "#fafad2",
- "lightgreen": "#90ee90",
- "lightgray": "#d3d3d3",
- "lightgrey": "#d3d3d3",
- "lightpink": "#ffb6c1",
- "lightsalmon": "#ffa07a",
- "lightseagreen": "#20b2aa",
- "lightskyblue": "#87cefa",
- "lightslategray": "#778899",
- "lightslategrey": "#778899",
- "lightsteelblue": "#b0c4de",
- "lightyellow": "#ffffe0",
- "lime": "#00ff00",
- "limegreen": "#32cd32",
- "linen": "#faf0e6",
- "magenta": "#ff00ff",
- "maroon": "#800000",
- "mediumaquamarine": "#66cdaa",
- "mediumblue": "#0000cd",
- "mediumorchid": "#ba55d3",
- "mediumpurple": "#9370db",
- "mediumseagreen": "#3cb371",
- "mediumslateblue": "#7b68ee",
- "mediumspringgreen": "#00fa9a",
- "mediumturquoise": "#48d1cc",
- "mediumvioletred": "#c71585",
- "midnightblue": "#191970",
- "mintcream": "#f5fffa",
- "mistyrose": "#ffe4e1",
- "moccasin": "#ffe4b5",
- "navajowhite": "#ffdead",
- "navy": "#000080",
- "oldlace": "#fdf5e6",
- "olive": "#808000",
- "olivedrab": "#6b8e23",
- "orange": "#ffa500",
- "orangered": "#ff4500",
- "orchid": "#da70d6",
- "palegoldenrod": "#eee8aa",
- "palegreen": "#98fb98",
- "paleturquoise": "#afeeee",
- "palevioletred": "#db7093",
- "papayawhip": "#ffefd5",
- "peachpuff": "#ffdab9",
- "peru": "#cd853f",
- "pink": "#ffc0cb",
- "plum": "#dda0dd",
- "powderblue": "#b0e0e6",
- "purple": "#800080",
- "red": "#ff0000",
- "rosybrown": "#bc8f8f",
- "royalblue": "#4169e1",
- "saddlebrown": "#8b4513",
- "salmon": "#fa8072",
- "sandybrown": "#f4a460",
- "seagreen": "#2e8b57",
- "seashell": "#fff5ee",
- "sienna": "#a0522d",
- "silver": "#c0c0c0",
- "skyblue": "#87ceeb",
- "slateblue": "#6a5acd",
- "slategray": "#708090",
- "slategrey": "#708090",
- "snow": "#fffafa",
- "springgreen": "#00ff7f",
- "steelblue": "#4682b4",
- "tan": "#d2b48c",
- "teal": "#008080",
- "thistle": "#d8bfd8",
- "tomato": "#ff6347",
- "turquoise": "#40e0d0",
- "violet": "#ee82ee",
- "wheat": "#f5deb3",
- "white": "#ffffff",
- "whitesmoke": "#f5f5f5",
- "yellow": "#ffff00",
- "yellowgreen": "#9acd32",
-}
diff --git a/image_occlusion_enhanced/Imaging/PIL/ImageFile.py b/image_occlusion_enhanced/Imaging/PIL/ImageFile.py
deleted file mode 100644
index 8a97c1b5..00000000
--- a/image_occlusion_enhanced/Imaging/PIL/ImageFile.py
+++ /dev/null
@@ -1,528 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# base class for image file handlers
-#
-# history:
-# 1995-09-09 fl Created
-# 1996-03-11 fl Fixed load mechanism.
-# 1996-04-15 fl Added pcx/xbm decoders.
-# 1996-04-30 fl Added encoders.
-# 1996-12-14 fl Added load helpers
-# 1997-01-11 fl Use encode_to_file where possible
-# 1997-08-27 fl Flush output in _save
-# 1998-03-05 fl Use memory mapping for some modes
-# 1999-02-04 fl Use memory mapping also for "I;16" and "I;16B"
-# 1999-05-31 fl Added image parser
-# 2000-10-12 fl Set readonly flag on memory-mapped images
-# 2002-03-20 fl Use better messages for common decoder errors
-# 2003-04-21 fl Fall back on mmap/map_buffer if map is not available
-# 2003-10-30 fl Added StubImageFile class
-# 2004-02-25 fl Made incremental parser more robust
-#
-# Copyright (c) 1997-2004 by Secret Labs AB
-# Copyright (c) 1995-2004 by Fredrik Lundh
-#
-# See the README file for information on usage and redistribution.
-#
-
-import Image
-import traceback, string, os
-
-MAXBLOCK = 65536
-
-SAFEBLOCK = 1024*1024
-
-ERRORS = {
- -1: "image buffer overrun error",
- -2: "decoding error",
- -3: "unknown error",
- -8: "bad configuration",
- -9: "out of memory error"
-}
-
-def raise_ioerror(error):
- try:
- message = Image.core.getcodecstatus(error)
- except AttributeError:
- message = ERRORS.get(error)
- if not message:
- message = "decoder error %d" % error
- raise IOError(message + " when reading image file")
-
-#
-# --------------------------------------------------------------------
-# Helpers
-
-def _tilesort(t1, t2):
- # sort on offset
- return cmp(t1[2], t2[2])
-
-#
-# --------------------------------------------------------------------
-# ImageFile base class
-
-##
-# Base class for image file handlers.
-
-class ImageFile(Image.Image):
- "Base class for image file format handlers."
-
- def __init__(self, fp=None, filename=None):
- Image.Image.__init__(self)
-
- self.tile = None
- self.readonly = 1 # until we know better
-
- self.decoderconfig = ()
- self.decodermaxblock = MAXBLOCK
-
- if Image.isStringType(fp):
- # filename
- self.fp = open(fp, "rb")
- self.filename = fp
- else:
- # stream
- self.fp = fp
- self.filename = filename
-
- try:
- self._open()
- except IndexError, v: # end of data
- if Image.DEBUG > 1:
- traceback.print_exc()
- raise SyntaxError, v
- except TypeError, v: # end of data (ord)
- if Image.DEBUG > 1:
- traceback.print_exc()
- raise SyntaxError, v
- except KeyError, v: # unsupported mode
- if Image.DEBUG > 1:
- traceback.print_exc()
- raise SyntaxError, v
- except EOFError, v: # got header but not the first frame
- if Image.DEBUG > 1:
- traceback.print_exc()
- raise SyntaxError, v
-
- if not self.mode or self.size[0] <= 0:
- raise SyntaxError, "not identified by this driver"
-
- def draft(self, mode, size):
- "Set draft mode"
-
- pass
-
- def verify(self):
- "Check file integrity"
-
- # raise exception if something's wrong. must be called
- # directly after open, and closes file when finished.
- self.fp = None
-
- def load(self):
- "Load image data based on tile list"
-
- pixel = Image.Image.load(self)
-
- if self.tile is None:
- raise IOError("cannot load this image")
- if not self.tile:
- return pixel
-
- self.map = None
-
- readonly = 0
-
- if self.filename and len(self.tile) == 1:
- # try memory mapping
- d, e, o, a = self.tile[0]
- if d == "raw" and a[0] == self.mode and a[0] in Image._MAPMODES:
- try:
- if hasattr(Image.core, "map"):
- # use built-in mapper
- self.map = Image.core.map(self.filename)
- self.map.seek(o)
- self.im = self.map.readimage(
- self.mode, self.size, a[1], a[2]
- )
- else:
- # use mmap, if possible
- import mmap
- file = open(self.filename, "r+")
- size = os.path.getsize(self.filename)
- # FIXME: on Unix, use PROT_READ etc
- self.map = mmap.mmap(file.fileno(), size)
- self.im = Image.core.map_buffer(
- self.map, self.size, d, e, o, a
- )
- readonly = 1
- except (AttributeError, EnvironmentError, ImportError):
- self.map = None
-
- self.load_prepare()
-
- # look for read/seek overrides
- try:
- read = self.load_read
- except AttributeError:
- read = self.fp.read
-
- try:
- seek = self.load_seek
- except AttributeError:
- seek = self.fp.seek
-
- if not self.map:
-
- # sort tiles in file order
- self.tile.sort(_tilesort)
-
- try:
- # FIXME: This is a hack to handle TIFF's JpegTables tag.
- prefix = self.tile_prefix
- except AttributeError:
- prefix = ""
-
- for d, e, o, a in self.tile:
- d = Image._getdecoder(self.mode, d, a, self.decoderconfig)
- seek(o)
- try:
- d.setimage(self.im, e)
- except ValueError:
- continue
- b = prefix
- t = len(b)
- while 1:
- s = read(self.decodermaxblock)
- if not s:
- self.tile = []
- raise IOError("image file is truncated (%d bytes not processed)" % len(b))
- b = b + s
- n, e = d.decode(b)
- if n < 0:
- break
- b = b[n:]
- t = t + n
-
- self.tile = []
- self.readonly = readonly
-
- self.fp = None # might be shared
-
- if not self.map and e < 0:
- raise_ioerror(e)
-
- # post processing
- if hasattr(self, "tile_post_rotate"):
- # FIXME: This is a hack to handle rotated PCD's
- self.im = self.im.rotate(self.tile_post_rotate)
- self.size = self.im.size
-
- self.load_end()
-
- return Image.Image.load(self)
-
- def load_prepare(self):
- # create image memory if necessary
- if not self.im or\
- self.im.mode != self.mode or self.im.size != self.size:
- self.im = Image.core.new(self.mode, self.size)
- # create palette (optional)
- if self.mode == "P":
- Image.Image.load(self)
-
- def load_end(self):
- # may be overridden
- pass
-
- # may be defined for contained formats
- # def load_seek(self, pos):
- # pass
-
- # may be defined for blocked formats (e.g. PNG)
- # def load_read(self, bytes):
- # pass
-
-##
-# Base class for stub image loaders.
-#
"
- "Using image to create new IO note.")
-
- if not image_path:
- tooltip(("This note cannot be edited, nor is there
"
- "an image to use for an image occlusion."))
- return False
-
- self.setPreservedAttrs(note)
- self.image_path = image_path
-
- width, height = imageProp(image_path)
- if not width:
- tooltip("Not a valid image file.")
- return False
-
- self.callImgOccEdit(width, height)
-
-
- def setPreservedAttrs(self, note):
- self.opref["tags"] = self.ed.tags.text()
- if self.origin == "addcards":
- self.opref["did"] = self.ed.parentWindow.deckChooser.selectedId()
- else:
- self.opref["did"] = mw.col.db.scalar(
- "select did from cards where id = ?", note.cards()[0].id)
-
-
- def getIONoteData(self, note):
- """Select image based on mode and set original field contents"""
-
- note_id = note[self.ioflds['id']]
- image_path = img2path(note[self.ioflds['im']])
- omask = img2path(note[self.ioflds['om']])
-
- if note_id == None or note_id.count("-") != 2:
- msg = "Editing unavailable: Invalid image occlusion Note ID"
- return msg, None
- elif not omask or not image_path:
- msg = "Editing unavailable: Missing image or original mask"
- return msg, None
-
- note_id_grps = note_id.split('-')
- self.opref["note_id"] = note_id
- self.opref["uniq_id"] = note_id_grps[0]
- self.opref["occl_tp"] = note_id_grps[1]
- self.opref["image"] = image_path
- self.opref["omask"] = omask
-
- return None, image_path
-
-
- def getImageFromFields(self, fields):
- """Parse fields for valid images"""
- image_path = None
- for fld in fields:
- image_path = img2path(fld)
- if image_path:
- break
- return image_path
-
-
- def getNewImage(self, parent=None, noclip=False):
- """Get image from file selection or clipboard"""
- if noclip:
- clip = None
- else:
- clip = QApplication.clipboard()
- if clip and clip.mimeData().imageData():
- handle, image_path = tempfile.mkstemp(suffix='.png')
- clip.image().save(image_path)
- clip.clear()
- if os.stat(image_path).st_size == 0:
- # workaround for a clipboard bug
- return self.getNewImage(noclip=True)
- else:
- return unicode(image_path)
-
- # retrieve last used image directory
- prev_image_dir = self.lconf["dir"]
- if not prev_image_dir or not os.path.isdir(prev_image_dir):
- prev_image_dir = IO_HOME
-
- image_path = QFileDialog.getOpenFileName(parent,
- "Select an Image", prev_image_dir,
- "Image Files (*.png *jpg *.jpeg *.gif)")
- image_path = unicode(image_path)
-
- if not image_path:
- return None
- elif not os.path.isfile(image_path):
- tooltip("Invalid image file path")
- return False
- else:
- self.lconf["dir"] = os.path.dirname(image_path)
- return image_path
-
-
- def callImgOccEdit(self, width, height):
- """Set up variables, call and prepare ImgOccEdit"""
- ofill = self.sconf['ofill']
- scol = self.sconf['scol']
- swidth = self.sconf['swidth']
- fsize = self.sconf['fsize']
- font = self.sconf['font']
-
- bkgd_url = path2url(self.image_path)
- opref = self.opref
- onote = self.ed.note
- flds = self.mflds
- deck = mw.col.decks.nameOrNone(opref["did"])
-
- try:
- mw.ImgOccEdit is not None
- mw.ImgOccEdit.resetWindow()
- # use existing IO instance when available
- except AttributeError:
- mw.ImgOccEdit = ImgOccEdit(mw)
- mw.ImgOccEdit.setupFields(flds)
- logging.debug("Launching new ImgOccEdit instance")
- dialog = mw.ImgOccEdit
- dialog.switchToMode(self.mode)
-
- url = QUrl.fromLocalFile(svg_edit_path)
- url.setQueryItems(svg_edit_queryitems)
- url.addQueryItem('initFill[color]', ofill)
- url.addQueryItem('dimensions', '{0},{1}'.format(width, height))
- url.addQueryItem('bkgd_url', bkgd_url)
- url.addQueryItem('initStroke[color]', scol)
- url.addQueryItem('initStroke[width]', str(swidth))
- url.addQueryItem('text[font_size]', str(fsize))
- url.addQueryItem('text[font_family]', "'%s', %s" % (font, svg_edit_fonts))
-
- if self.mode != "add":
- url.addQueryItem('initTool', 'select'),
- for i in flds:
- fn = i["name"]
- if fn in self.ioflds_priv:
- continue
- dialog.tedit[fn].setPlainText(onote[fn].replace('
', '\n'))
- svg_url = path2url(opref["omask"])
- url.addQueryItem('url', svg_url)
- else:
- url.addQueryItem('initTool', 'rect'),
-
- dialog.svg_edit.setUrl(url)
- dialog.deckChooser.deck.setText(deck)
- dialog.tags_edit.setCol(mw.col)
- dialog.tags_edit.setText(opref["tags"])
-
- if onote:
- for i in self.ioflds_prsv:
- if i in onote:
- dialog.tedit[i].setPlainText(onote[i])
-
- dialog.visible = True
- if self.mode == "add":
- dialog.show()
- else:
- # modal dialog when editing
- dialog.exec_()
-
-
- def onChangeImage(self):
- """Change canvas background image"""
- image_path = self.getNewImage()
- if not image_path:
- return False
- width, height = imageProp(image_path)
- if not width:
- tooltip("Not a valid image file.")
- return False
- bkgd_url = path2url(image_path)
- mw.ImgOccEdit.svg_edit.eval("""
- svgCanvas.setBackground('#FFF', '%s');
- svgCanvas.setResolution(%s, %s);
- //svgCanvas.zoomChanged('', 'canvas');
- """ %(bkgd_url, width, height))
- self.image_path = image_path
-
-
- def onAddNotesButton(self, choice, close):
- """Get occlusion settings in and pass them to the note generator (add)"""
- dialog = mw.ImgOccEdit
- svg_edit = dialog.svg_edit
- svg = svg_edit.page().mainFrame().evaluateJavaScript(
- "svgCanvas.svgCanvasToString();")
- svg = unicode(svg) # store svg as unicode string
-
- r1 = self.getUserInputs(dialog)
- if r1 == False:
- return False
- (fields, tags) = r1
- did = dialog.deckChooser.selectedId()
-
- noteGenerator = genByKey(choice)
- gen = noteGenerator(self.ed, svg, self.image_path,
- self.opref, tags, fields, did)
- r = gen.generateNotes()
- if r == False:
- return False
-
- if self.origin == "addcards" and self.ed.note:
- # Update Editor with modified tags and sources field
- self.ed.tags.setText(" ".join(tags))
- self.ed.saveTags()
- for i in self.ioflds_prsv:
- if i in self.ed.note:
- self.ed.note[i] = fields[i]
- self.ed.loadNote()
- deck = mw.col.decks.nameOrNone(did)
- self.ed.parentWindow.deckChooser.deck.setText(deck)
-
- if close:
- dialog.close()
-
- mw.reset()
-
-
- def onEditNotesButton(self, choice):
- """Get occlusion settings and pass them to the note generator (edit)"""
- dialog = mw.ImgOccEdit
- svg_edit = dialog.svg_edit
- svg = svg_edit.page().mainFrame().evaluateJavaScript(
- "svgCanvas.svgCanvasToString();")
-
- r1 = self.getUserInputs(dialog, edit=True)
- if r1 == False:
- return False
- (fields, tags) = r1
- did = self.opref["did"]
- old_occl_tp = self.opref["occl_tp"]
-
- noteGenerator = genByKey(choice, old_occl_tp)
- gen = noteGenerator(self.ed, svg, self.image_path,
- self.opref, tags, fields, did)
- r = gen.updateNotes()
- if r == False:
- return False
-
- mw.ImgOccEdit.close()
-
- if r == "reset":
- # modifications to mask require media collection reset
- ## refresh webview image cache
- QWebSettings.clearMemoryCaches()
- ## write a dummy file to update collection.media modtime and force sync
- media_dir = mw.col.media.dir()
- fpath = os.path.join(media_dir, "syncdummy.txt")
- if not os.path.isfile(fpath):
- with open(fpath, "w") as f:
- f.write("io sync dummy")
- os.remove(fpath)
-
- mw.reset() # FIXME: causes glitches in editcurrent mode
-
-
- def getUserInputs(self, dialog, edit=False):
- """Get fields and tags from ImgOccEdit while checking note type"""
- fields = {}
- # note type integrity check:
- io_model_fields = mw.col.models.fieldNames(self.model)
- if not all(x in io_model_fields for x in self.ioflds.values()):
- ioError("Error: Image Occlusion note type " \
- "not configured properly.Please make sure you did not " \
- "manually delete or rename any of the default fields.",
- help="notetype")
- return False
- for i in self.mflds:
- fn = i['name']
- if fn in self.ioflds_priv:
- continue
- if edit and fn in self.sconf["skip"]:
- continue
- text = dialog.tedit[fn].toPlainText().replace('\n', '
')
- fields[fn] = text
- tags = dialog.tags_edit.text().split()
- return (fields, tags)
diff --git a/image_occlusion_enhanced/config.py b/image_occlusion_enhanced/config.py
deleted file mode 100644
index 9c69f50d..00000000
--- a/image_occlusion_enhanced/config.py
+++ /dev/null
@@ -1,133 +0,0 @@
-# -*- coding: utf-8 -*-
-####################################################
-## ##
-## Image Occlusion Enhanced ##
-## ##
-## Copyright (c) Glutanimate 2016-2017 ##
-## (https://github.com/Glutanimate) ##
-## ##
-####################################################
-
-"""
-Sets up configuration, including constants
-"""
-
-import os
-import sys
-
-from aqt import mw
-
-global IO_FLDS, IO_FLDS_IDS
-global IO_MODEL_NAME, IO_CARD_NAME, IO_HOME, IO_HOTKEY
-
-IO_MODEL_NAME = "Image Occlusion Enhanced"
-IO_CARD_NAME = "IO Card"
-
-IO_FLDS = {
- 'id': u"ID (hidden)",
- 'hd': u"Header",
- 'im': u"Image",
- 'ft': u"Footer",
- 'rk': u"Remarks",
- 'sc': u"Sources",
- 'e1': u"Extra 1",
- 'e2': u"Extra 2",
- 'qm': u"Question Mask",
- 'am': u"Answer Mask",
- 'om': u"Original Mask"
-}
-
-IO_FLDS_IDS = ["id", "hd", "im", "qm", "ft", "rk",
- "sc", "e1", "e2", "am", "om"]
-
-# TODO: Use IDs instead of names to make these compatible with self.ioflds
-
-# fields that aren't user-editable
-IO_FIDS_PRIV = ['id', 'im', 'qm', 'am', 'om']
-
-# fields that are synced between an IO Editor session and Anki's Editor
-IO_FIDS_PRSV = ['sc']
-
-# variables for local preference handling
-sys_encoding = sys.getfilesystemencoding()
-IO_HOME = os.path.expanduser('~').decode(sys_encoding)
-IO_HOTKEY = "Ctrl+Shift+O"
-
-# default configurations
-default_conf_local = {'version': 1.01,
- 'dir': IO_HOME,
- 'hotkey': IO_HOTKEY}
-default_conf_syncd = {'version': 1.01,
- 'ofill': 'FFEBA2',
- 'qfill': 'FF7E7E',
- 'scol': '2D2D2D',
- 'swidth': 3,
- 'font': 'Arial',
- 'fsize': 24,
- 'skip': [IO_FLDS["e1"], IO_FLDS["e2"]],
- 'flds': IO_FLDS}
-
-import template
-
-def loadConfig(self):
- """load and/or create add-on preferences"""
- # Synced preferences
- if not 'imgocc' in mw.col.conf:
- # create initial configuration
- mw.col.conf['imgocc'] = default_conf_syncd
-
- # upgrade from earlier IO versions:
- if 'image_occlusion_conf' in mw.col.conf:
- old_conf = mw.col.conf['image_occlusion_conf']
- mw.col.conf['imgocc']['ofill'] = old_conf['initFill[color]']
- mw.col.conf['imgocc']['qfill'] = old_conf['mask_fill_color']
- # insert other upgrade actions here
- mw.col.setMod()
-
- elif mw.col.conf['imgocc']['version'] < default_conf_syncd['version']:
- print "Updating config DB from earlier IO release"
- for key in default_conf_syncd.keys():
- if key not in mw.col.conf['imgocc']:
- mw.col.conf['imgocc'][key] = default_conf_syncd[key]
- mw.col.conf['imgocc']['version'] = default_conf_syncd['version']
- # insert other update actions here:
- # template.update_template(mw.col) # update card templates
- mw.col.setMod()
-
-
- # Local preferences
- if not 'imgocc' in mw.pm.profile:
- mw.pm.profile["imgocc"] = default_conf_local
- elif mw.pm.profile['imgocc'].get('version', 0) < default_conf_syncd['version']:
- for key in default_conf_local.keys():
- if key not in mw.col.conf['imgocc']:
- mw.pm.profile["imgocc"][key] = default_conf_local[key]
- mw.pm.profile['imgocc']['version'] = default_conf_local['version']
-
- model = mw.col.models.byName(IO_MODEL_NAME)
- if not model:
- # create model and set up default field name config
- model = template.add_io_model(mw.col)
- mw.col.conf['imgocc']['flds'] = default_conf_syncd['flds']
- mflds = model['flds']
- ioflds = mw.col.conf['imgocc']['flds']
- ioflds_priv = []
- for i in IO_FIDS_PRIV:
- ioflds_priv.append(ioflds[i])
- # preserve fields if they are marked as sticky in the IO note type:
- ioflds_prsv = []
- for fld in mflds:
- fname = fld['name']
- if fld['sticky'] and fname not in ioflds_priv:
- ioflds_prsv.append(fname)
-
- self.sconf_dflt = default_conf_syncd
- self.lconf_dflt = default_conf_local
- self.sconf = mw.col.conf['imgocc']
- self.lconf = mw.pm.profile["imgocc"]
-
- self.ioflds = ioflds
- self.ioflds_priv = ioflds_priv
- self.ioflds_prsv = ioflds_prsv
- self.model = model
- self.mflds = mflds
diff --git a/image_occlusion_enhanced/dialogs.py b/image_occlusion_enhanced/dialogs.py
deleted file mode 100644
index 96f120db..00000000
--- a/image_occlusion_enhanced/dialogs.py
+++ /dev/null
@@ -1,172 +0,0 @@
-# -*- coding: utf-8 -*-
-####################################################
-## ##
-## Image Occlusion Enhanced ##
-## ##
-## Copyright (c) Glutanimate 2016-2017 ##
-## (https://github.com/Glutanimate) ##
-## ##
-## Based on Image Occlusion 2.0 ##
-## Copyright (c) 2012-2015 tmbb ##
-## (https://github.com/tmbb) ##
-## ##
-####################################################
-
-"""
-Handles all minor utility dialogs
-"""
-
-import logging, sys
-
-from PyQt4 import QtCore, QtGui
-from aqt.qt import *
-from anki.errors import AnkiError
-
-from aqt import mw, webview, deckchooser, tagedit
-from aqt.utils import saveGeom, restoreGeom
-
-from config import *
-from resources import *
-
-
-def ioError(text, title="Image Occlusion Enhanced Error",
- parent=None, help=None):
- msgfunc = QMessageBox.critical
- btns = None
- if help:
- btns = QMessageBox.Help | QMessageBox.Ok
- while 1:
- r = ioInfo(text, title, parent, buttons=btns, msgfunc=msgfunc)
- if r == QMessageBox.Help:
- ioHelp(help, parent=parent)
- else:
- break
- return r
-
-def ioAskUser(text, title="Image Occlusion Enhanced", parent=None,
- help="", defaultno=False, msgfunc=None):
- """Show a yes/no question. Return true if yes.
- based on askUser by Damien Elmes"""
-
- msgfunc = QMessageBox.question
- btns = QMessageBox.Yes | QMessageBox.No
- if help:
- btns |= QMessageBox.Help
- while 1:
- if defaultno:
- default = QMessageBox.No
- else:
- default = QMessageBox.Yes
- r = ioInfo(text, title, parent, btns, default, msgfunc)
- if r == QMessageBox.Help:
- ioHelp(help)
- else:
- break
- return r == QMessageBox.Yes
-
-def ioInfo(text, title="Image Occlusion Enhanced", parent=None,
- buttons=None, default=None, msgfunc=None):
- if not parent:
- parent = mw.app.activeWindow()
- if not buttons:
- buttons = QMessageBox.Ok
- if not default:
- default = QMessageBox.Ok
- if not msgfunc:
- msgfunc = QMessageBox.information
- return msgfunc(parent, title, text, buttons, default)
-
-
-io_link_wiki = "https://github.com/Glutanimate/image-occlusion-enhanced/wiki"
-io_link_tut = "https://www.youtube.com/playlist?list=PL3MozITKTz5YFHDGB19ypxcYfJ1ITk_6o"
-io_link_thread = ("https://anki.tenderapp.com/discussions/add-ons/"
- "8295-image-occlusion-enhanced-official-thread")
-help_text = {}
-help_text["add"] = u"""
-
-
-
-
-
-
-
Each mask shape represents a card.\
- Removing any of the existing shapes will remove the corresponding card.\
- New shapes will generate new cards. You can change the occlusion type\
- by using the dropdown box on the left.
If you click on the \
- Add new cards button a completely new batch of cards will be \
- generated, leaving your originals untouched.
\
- Actions performed in Image Occlusion's Editing Mode cannot be\
- easily undone, so please make sure to check your changes twice before\
- applying them.
The only exception to this are purely textual\
- changes to fields like the header or footer of your notes. These can\
- be fully reverted by using Ctrl+Z in the Browser or Reviewer view.
\
- More information: Wiki: Editing Notes.\
- """ % (io_link_wiki + "/Basic-Use#editing-cards")
-help_text["notetype"] = """Fixing a broken note type:\
-
The Image Occlusion Enhanced note type can't be edited \
- arbitrarily. If you delete a field that's required by the add-on \
- or rename it outside of the IO Options dialog you will be presented \
- with an error message.
- To fix this issue please follow the instructions in the \
- wiki.""" % (io_link_wiki + "/Troubleshooting#note-type")
-help_text["main"] = u"""Help and Support
-
- Credits and License
-
- """ % (io_link_wiki, io_link_tut, io_link_thread)
-
-def ioHelp(help, title=None, text=None, parent=None):
- """Display an info message or a predefined help section"""
- if help != "custom":
- text = help_text[help]
- if not title:
- title = "Image Occlusion Enhanced Help"
- if not parent:
- parent = mw.app.activeWindow()
- mbox = QMessageBox(parent)
- mbox.setAttribute(Qt.WA_DeleteOnClose)
- mbox.setStandardButtons(QMessageBox.Ok)
- mbox.setWindowTitle(title)
- mbox.setText(text)
- mbox.setWindowModality(Qt.NonModal)
- mbox.show()
\ No newline at end of file
diff --git a/image_occlusion_enhanced/editor.py b/image_occlusion_enhanced/editor.py
deleted file mode 100644
index 492a52d3..00000000
--- a/image_occlusion_enhanced/editor.py
+++ /dev/null
@@ -1,364 +0,0 @@
-# -*- coding: utf-8 -*-
-####################################################
-## ##
-## Image Occlusion Enhanced ##
-## ##
-## Copyright (c) Glutanimate 2016-2017 ##
-## (https://github.com/Glutanimate) ##
-## ##
-## Based on Image Occlusion 2.0 ##
-## Copyright (c) 2012-2015 tmbb ##
-## (https://github.com/tmbb) ##
-## ##
-####################################################
-
-"""
-Image Occlusion editor dialog
-"""
-
-from PyQt4 import QtCore, QtGui
-from aqt.qt import *
-
-from aqt import mw, webview, deckchooser, tagedit
-from aqt.utils import saveGeom, restoreGeom
-
-from dialogs import ioHelp
-from config import *
-
-class ImgOccEdit(QDialog):
- """Main Image Occlusion Editor dialog"""
- def __init__(self, mw):
- QDialog.__init__(self, parent=None)
- self.setWindowFlags(Qt.Window)
- self.visible = False
- self.mode = "add"
- loadConfig(self)
- self.setupUi()
- restoreGeom(self, "imgoccedit")
-
- def closeEvent(self, event):
- if mw.pm.profile is not None:
- self.deckChooser.cleanup()
- saveGeom(self, "imgoccedit")
- self.visible = False
- event.accept()
-
- def reject(self):
- # Override QDialog Esc key reject
- pass
-
- def setupUi(self):
- """Set up ImgOccEdit UI"""
- # Main widgets aside from fields
- self.svg_edit = webview.AnkiWebView()
- # required for local href links to work properly (e.g. context menu):
- self.svg_edit.page().setLinkDelegationPolicy(QWebPage.DelegateExternalLinks)
- self.svg_edit.setCanFocus(True) # focus necessary for hotkeys
- self.tags_hbox = QHBoxLayout()
- self.tags_edit = tagedit.TagEdit(self)
- self.tags_label = QLabel("Tags")
- self.tags_label.setFixedWidth(70)
- self.deck_container = QWidget()
- self.deckChooser = deckchooser.DeckChooser(mw,
- self.deck_container, label=True)
- self.deckChooser.deck.setAutoDefault(False)
-
- # workaround for tab focus order issue of the tags entry
- # (this particular section is only needed when the quick deck
- # buttons add-on is installed)
- if self.deck_container.layout().children(): # multiple deck buttons
- for i in range(self.deck_container.layout().children()[0].count()):
- try:
- item = self.deck_container.layout().children()[0].itemAt(i)
- # remove Tab focus manually:
- item.widget().setFocusPolicy(Qt.ClickFocus)
- item.widget().setAutoDefault(False)
- except AttributeError:
- pass
-
-
- # Button row widgets
- self.bottom_label = QLabel()
- button_box = QtGui.QDialogButtonBox(QtCore.Qt.Horizontal, self)
- button_box.setCenterButtons(False)
-
- image_btn = QPushButton("Change &Image", clicked=self.changeImage)
- image_btn.setIcon(QIcon(":/icons/new_occlusion.png"))
- image_btn.setIconSize(QSize(16, 16))
- image_btn.setAutoDefault(False)
- help_btn = QPushButton("&Help", clicked=self.onHelp)
- help_btn.setAutoDefault(False)
-
- self.occl_tp_select = QComboBox()
- self.occl_tp_select.addItems(["Don't Change", "Hide All, Reveal One",
- "Hide One, Reveal All"])
-
- self.edit_btn = button_box.addButton("&Edit Cards",
- QDialogButtonBox.ActionRole)
- self.new_btn = button_box.addButton("&Add New Cards",
- QDialogButtonBox.ActionRole)
- self.ao_btn = button_box.addButton(u"Hide &All, Reveal One",
- QDialogButtonBox.ActionRole)
- self.oa_btn = button_box.addButton(u"Hide &One, Reveal All",
- QDialogButtonBox.ActionRole)
- close_button = button_box.addButton("&Close",
- QDialogButtonBox.RejectRole)
-
- image_tt = ("Switch to a different image while preserving all of "
- "the shapes and fields")
- dc_tt = "Preserve existing occlusion type"
- edit_tt = "Edit all cards using current mask shapes and field entries"
- new_tt = "Create new batch of cards without editing existing ones"
- ao_tt = ("Generate cards with nonoverlapping information, where all
"
- "labels are hidden on the front and one revealed on the back")
- oa_tt = ("Generate cards with overlapping information, where one
"
- "label is hidden on the front and revealed on the back")
- close_tt = "Close Image Occlusion Editor without generating cards"
-
- image_btn.setToolTip(image_tt)
- self.edit_btn.setToolTip(edit_tt)
- self.new_btn.setToolTip(new_tt)
- self.ao_btn.setToolTip(ao_tt)
- self.oa_btn.setToolTip(oa_tt)
- close_button.setToolTip(close_tt)
- self.occl_tp_select.setItemData(0, dc_tt, Qt.ToolTipRole)
- self.occl_tp_select.setItemData(1, ao_tt, Qt.ToolTipRole)
- self.occl_tp_select.setItemData(2, oa_tt, Qt.ToolTipRole)
-
- for btn in [image_btn, self.edit_btn, self.new_btn, self.ao_btn,
- self.oa_btn, close_button]:
- btn.setFocusPolicy(Qt.ClickFocus)
-
- self.connect(self.edit_btn, SIGNAL("clicked()"), self.editNote)
- self.connect(self.new_btn, SIGNAL("clicked()"), self.new)
- self.connect(self.ao_btn, SIGNAL("clicked()"), self.addAO)
- self.connect(self.oa_btn, SIGNAL("clicked()"), self.addOA)
- self.connect(close_button, SIGNAL("clicked()"), self.close)
-
- # Set basic layout up
-
- ## Button row
- bottom_hbox = QHBoxLayout()
- bottom_hbox.addWidget(image_btn)
- bottom_hbox.addWidget(help_btn)
- bottom_hbox.insertStretch(2, stretch=1)
- bottom_hbox.addWidget(self.bottom_label)
- bottom_hbox.addWidget(self.occl_tp_select)
- bottom_hbox.addWidget(button_box)
-
- ## Tab 1
- vbox1 = QVBoxLayout()
- vbox1.addWidget(self.svg_edit, stretch=1)
-
- ## Tab 2
- self.vbox2 = QVBoxLayout()
- # vbox2 fields are variable and added by setupFields() at a later point
-
- ## Main Tab Widget
- tab1 = QWidget()
- self.tab2 = QWidget()
- tab1.setLayout(vbox1)
- self.tab2.setLayout(self.vbox2)
- self.tab_widget = QtGui.QTabWidget()
- self.tab_widget.setFocusPolicy(Qt.ClickFocus)
- self.tab_widget.addTab(tab1,"&Masks Editor")
- self.tab_widget.addTab(self.tab2,"&Fields")
- self.tab_widget.setTabToolTip(1, "Include additional information (optional)")
- self.tab_widget.setTabToolTip(0, "Create image occlusion masks (required)")
-
- ## Main Window
- vbox_main = QVBoxLayout()
- vbox_main.setMargin(5)
- vbox_main.addWidget(self.tab_widget)
- vbox_main.addLayout(bottom_hbox)
- self.setLayout(vbox_main)
- self.setMinimumWidth(640)
- self.tab_widget.setCurrentIndex(0)
- self.svg_edit.setFocus()
-
- # Define and connect key bindings
-
- ## Field focus hotkeys
- for i in range(1,10):
- s = self.connect(QtGui.QShortcut(QtGui.QKeySequence("Ctrl+%i" %i), self),
- QtCore.SIGNAL('activated()'),
- lambda f=i-1:self.focusField(f))
- ## Other hotkeys
- self.connect(QtGui.QShortcut(QtGui.QKeySequence("Ctrl+Return"), self),
- QtCore.SIGNAL('activated()'), lambda: self.defaultAction(True))
- self.connect(QtGui.QShortcut(QtGui.QKeySequence("Ctrl+Shift+Return"), self),
- QtCore.SIGNAL('activated()'), lambda: self.addOA(True))
- self.connect(QtGui.QShortcut(QtGui.QKeySequence("Ctrl+Tab"), self),
- QtCore.SIGNAL('activated()'), self.switchTabs)
- self.connect(QtGui.QShortcut(QtGui.QKeySequence("Ctrl+r"), self),
- QtCore.SIGNAL('activated()'), self.resetMainFields)
- self.connect(QtGui.QShortcut(QtGui.QKeySequence("Ctrl+Shift+r"), self),
- QtCore.SIGNAL('activated()'), self.resetAllFields)
- self.connect(QtGui.QShortcut(QtGui.QKeySequence("Ctrl+Shift+t"), self),
- QtCore.SIGNAL('activated()'), self.focusTags)
- self.connect(QtGui.QShortcut(QtGui.QKeySequence("Ctrl+f"), self),
- QtCore.SIGNAL('activated()'), self.fitImageCanvas)
-
-
- # Various actions that act on / interact with the ImgOccEdit UI:
-
- # Note actions
-
- def changeImage(self):
- mw.ImgOccAdd.onChangeImage()
- def defaultAction(self, close):
- if self.mode == "add":
- self.addAO(close)
- else:
- self.editNote()
- def addAO(self, close=False):
- mw.ImgOccAdd.onAddNotesButton("ao", close)
- def addOA(self, close=False):
- mw.ImgOccAdd.onAddNotesButton("oa", close)
- def new(self, close=False):
- choice = self.occl_tp_select.currentText()
- mw.ImgOccAdd.onAddNotesButton(choice, close)
- def editNote(self):
- choice = self.occl_tp_select.currentText()
- mw.ImgOccAdd.onEditNotesButton(choice)
- def onHelp(self):
- if self.mode == "add":
- ioHelp("add")
- else:
- ioHelp("edit")
-
-
- # Window state
-
- def resetFields(self):
- """Reset all widgets. Needed for changes to the note type"""
- layout = self.vbox2
- for i in reversed(range(layout.count())):
- item = layout.takeAt(i)
- layout.removeItem(item)
- if item.widget():
- item.widget().setParent(None)
- elif item.layout():
- sublayout = item.layout()
- sublayout.setParent(None)
- for i in reversed(range(sublayout.count())):
- subitem = sublayout.takeAt(i)
- sublayout.removeItem(subitem)
- subitem.widget().setParent(None)
- self.tags_hbox.setParent(None)
-
- def resetWindow(self):
- """Fully reset window state"""
- self.resetAllFields()
- self.tab_widget.setCurrentIndex(0)
- self.occl_tp_select.setCurrentIndex(0)
- self.tedit[self.ioflds["hd"]].setFocus()
- self.svg_edit.setFocus()
-
- def setupFields(self, flds):
- """Setup dialog text edits based on note type fields"""
- self.tedit = {}
- self.tlabel = {}
- self.flds = flds
- for i in flds:
- if i['name'] in self.ioflds_priv:
- continue
- hbox = QHBoxLayout()
- tedit = QPlainTextEdit()
- label = QLabel(i["name"])
- hbox.addWidget(label)
- hbox.addWidget(tedit)
- tedit.setTabChangesFocus(True)
- tedit.setMinimumHeight(40)
- label.setFixedWidth(70)
- self.tedit[i["name"]] = tedit
- self.tlabel[i["name"]] = label
- self.vbox2.addLayout(hbox)
-
- self.tags_hbox.addWidget(self.tags_label)
- self.tags_hbox.addWidget(self.tags_edit)
- self.vbox2.addLayout(self.tags_hbox)
- self.vbox2.addWidget(self.deck_container)
- # switch Tab focus order of deckchooser and tags_edit (
- # for some reason it's the wrong way around by default):
- self.tab2.setTabOrder(self.tags_edit, self.deckChooser.deck)
-
- def switchToMode(self, mode):
- """Toggle between add and edit layouts"""
- hide_on_add = [self.occl_tp_select, self.edit_btn, self.new_btn]
- hide_on_edit = [self.ao_btn, self.oa_btn]
- self.mode = mode
- for i in self.tedit.values():
- i.show()
- for i in self.tlabel.values():
- i.show()
- if mode == "add":
- for i in hide_on_add:
- i.hide()
- for i in hide_on_edit:
- i.show()
- dl_txt = "Deck"
- ttl = "Image Occlusion Enhanced - Add Mode"
- bl_txt = "Add Cards:"
- else:
- for i in hide_on_add:
- i.show()
- for i in hide_on_edit:
- i.hide()
- for i in self.sconf['skip']:
- if i in self.tedit.keys():
- self.tedit[i].hide()
- self.tlabel[i].hide()
- dl_txt = "Deck for Add new cards"
- ttl = "Image Occlusion Enhanced - Editing Mode"
- bl_txt = "Type:"
- self.deckChooser.deckLabel.setText(dl_txt)
- self.setWindowTitle(ttl)
- self.bottom_label.setText(bl_txt)
-
- # Other actions
-
- def switchTabs(self):
- currentTab = self.tab_widget.currentIndex()
- if currentTab == 0:
- self.tab_widget.setCurrentIndex(1)
- if isinstance(QApplication.focusWidget(), QPushButton):
- self.tedit[self.ioflds["hd"]].setFocus()
- else:
- self.tab_widget.setCurrentIndex(0)
-
- def focusField(self, idx):
- """Focus field in vbox2 layout by index number"""
- self.tab_widget.setCurrentIndex(1)
- target_item = self.vbox2.itemAt(idx)
- if not target_item:
- return
- target_layout = target_item.layout()
- target_widget = target_item.widget()
- if target_layout:
- target = target_layout.itemAt(1).widget()
- elif target_widget:
- target = target_widget
- target.setFocus()
-
- def focusTags(self):
- self.tab_widget.setCurrentIndex(1)
- self.tags_edit.setFocus()
-
- def resetMainFields(self):
- """Reset all fields aside from sticky ones"""
- for i in self.flds:
- fn = i['name']
- if fn in self.ioflds_priv or fn in self.ioflds_prsv:
- continue
- self.tedit[fn].setPlainText("")
-
- def resetAllFields(self):
- """Reset all fields"""
- self.resetMainFields()
- for i in self.ioflds_prsv:
- self.tedit[i].setPlainText("")
-
- def fitImageCanvas(self):
- command = "svgCanvas.zoomChanged('', 'canvas');"
- self.svg_edit.eval(command)
diff --git a/image_occlusion_enhanced/main.py b/image_occlusion_enhanced/main.py
deleted file mode 100644
index 4de1c695..00000000
--- a/image_occlusion_enhanced/main.py
+++ /dev/null
@@ -1,201 +0,0 @@
-# -*- coding: utf-8 -*-
-####################################################
-## ##
-## Image Occlusion Enhanced ##
-## ##
-## Copyright (c) Glutanimate 2016-2017 ##
-## (https://github.com/Glutanimate) ##
-## ##
-## Based on Image Occlusion 2.0 ##
-## Copyright (c) 2012-2015 tmbb ##
-## (https://github.com/tmbb) ##
-## ##
-####################################################
-
-"""
-Sets up buttons and menus and calls other modules.
-"""
-
-import logging, sys
-
-from anki.lang import _
-from aqt.qt import *
-
-from aqt import mw
-from aqt.editor import Editor, EditorWebView
-from aqt.addcards import AddCards
-from aqt.editcurrent import EditCurrent
-from aqt.reviewer import Reviewer
-from aqt.utils import tooltip
-from anki.hooks import wrap, addHook, runHook
-
-from config import *
-from resources import *
-from add import ImgOccAdd
-from options import ImgOccOpts
-from dialogs import ioHelp, ioError
-
-logging.basicConfig(stream=sys.stdout, level=logging.ERROR)
-
-
-def onIoSettings(mw):
- """Call settings dialog if Editor not active"""
- if hasattr(mw, "ImgOccEdit") and mw.ImgOccEdit.visible:
- tooltip("Please close Image Occlusion Editor\
- to access the Options.")
- return
- dialog = ImgOccOpts(mw)
- dialog.exec_()
-
-
-def onIoHelp():
- """Call main help dialog"""
- ioHelp("main")
-
-
-def onImgOccButton(self, origin=None, image_path=None):
- """Launch Image Occlusion Enhanced"""
- origin = origin or getEdParentInstance(self.parentWindow)
- io_model = mw.col.models.byName(IO_MODEL_NAME)
- if io_model:
- io_model_fields = mw.col.models.fieldNames(io_model)
- if "imgocc" in mw.col.conf:
- dflt_fields = mw.col.conf['imgocc']['flds'].values()
- else:
- dflt_fields = IO_FLDS.values()
- # note type integrity check
- if not all(x in io_model_fields for x in dflt_fields):
- ioError("Error: Image Occlusion note type " \
- "not configured properly.Please make sure you did not " \
- "manually delete or rename any of the default fields.",
- help="notetype")
- return False
- try: # allows us to fall back to old image if necessary
- oldimg = mw.ImgOccAdd.image_path
- except AttributeError:
- oldimg = None
- mw.ImgOccAdd = ImgOccAdd(self, origin, oldimg)
- mw.ImgOccAdd.occlude(image_path)
-
-
-def onSetupEditorButtons(self):
- """Add IO button to Editor"""
- conf = mw.pm.profile.get("imgocc")
- if not conf:
- hotkey = IO_HOTKEY
- else:
- hotkey = conf.get("hotkey", IO_HOTKEY)
-
- origin = getEdParentInstance(self.parentWindow)
-
- if origin == "addcards":
- tt = "Add Image Occlusion"
- icon = "new_occlusion"
- else:
- tt = "Edit Image Occlusion"
- icon = "new_occlusion"
-
- btn = self._addButton(icon, lambda o=self: onImgOccButton(self, origin),
- _(hotkey), _(u"{} ({})".format(tt, hotkey)), canDisable=False)
-
-
-def getEdParentInstance(parent):
- """Determine parent instance of editor widget"""
- if isinstance(parent, AddCards):
- return "addcards"
- elif isinstance(parent, EditCurrent):
- return "editcurrent"
- else:
- return "browser"
-
-
-def openImage(path):
- """Open path with default system app"""
- import subprocess
- try:
- if sys.platform=='win32':
- os.startfile(path)
- elif sys.platform=='darwin':
- subprocess.Popen(['open', path])
- else:
- subprocess.Popen(['xdg-open', path])
- except OSError:
- QDesktopServices.openUrl(QUrl("file://" + path))
-
-
-def contextMenuEvent(self, evt):
- """Add custom context menu for images"""
- m = QMenu(self)
- a = m.addAction(_("Cut"))
- a.triggered.connect(self.onCut)
- a = m.addAction(_("Copy"))
- a.triggered.connect(self.onCopy)
- a = m.addAction(_("Paste"))
- a.triggered.connect(self.onPaste)
- ##################################################
- hit = self.page().currentFrame().hitTestContent(evt.pos())
- url = hit.imageUrl()
- if url.isValid():
- image_url = url.toLocalFile()
- a = m.addAction(_("Occlude Image"))
- a.triggered.connect(
- lambda _, u=image_url, s=self.editor: onImgOccButton(
- s, image_path=u))
- a = m.addAction(_("Open Image"))
- a.triggered.connect(lambda _, u=image_url: openImage(u))
- ##################################################
- runHook("EditorWebView.contextMenuEvent", self, m)
- m.popup(QCursor.pos())
-
-
-def onSetNote(self, note, hide=True, focus=False):
- """Customize the editor when IO notes are active"""
- if not (self.note and self.note.model()["name"] == IO_MODEL_NAME):
- return
- # simple hack to hide the ID field if it's the first one
- if self.note.model()['flds'][0]['name'] == IO_FLDS['id']:
- self.web.eval("""
- // hide first fname, field, and snowflake (FrozenFields add-on)
- document.styleSheets[0].addRule(
- 'tr:first-child .fname, #f0, #i0', 'display: none;');
- """)
- # Limit image display height
- self.web.eval("""
- document.styleSheets[0].addRule(
- 'img', 'max-width: 90%; max-height: 160px');
- """)
-
-
-def newKeyHandler(self, evt):
- """Bind mask reveal to a hotkey"""
- if (self.state == "answer" and evt.key() == Qt.Key_G):
- self.web.eval('document.getElementById("io-revl-btn").click();')
-
-
-def onShowAnswer(self, _old):
- """Retain scroll position across answering the card"""
- if not self.card or not self.card.model()["name"] == IO_MODEL_NAME:
- return _old(self)
- scroll_pos = self.web.page().mainFrame().scrollPosition()
- ret = _old(self)
- self.web.page().mainFrame().setScrollPosition(scroll_pos)
- return ret
-
-
-# Set up menus
-options_action = QAction("Image &Occlusion Enhanced Options...", mw)
-help_action = QAction("Image &Occlusion Enhanced...", mw)
-mw.connect(options_action, SIGNAL("triggered()"),
- lambda o=mw: onIoSettings(o))
-mw.connect(help_action, SIGNAL("triggered()"),
- onIoHelp)
-mw.form.menuTools.addAction(options_action)
-mw.form.menuHelp.addAction(help_action)
-
-# Set up hooks
-addHook('setupEditorButtons', onSetupEditorButtons)
-EditorWebView.contextMenuEvent = contextMenuEvent
-Editor.setNote = wrap(Editor.setNote, onSetNote, "after")
-Editor.onImgOccButton = onImgOccButton
-Reviewer._keyHandler = wrap(Reviewer._keyHandler, newKeyHandler, "before")
-Reviewer._showAnswer = wrap(Reviewer._showAnswer, onShowAnswer, "around")
\ No newline at end of file
diff --git a/image_occlusion_enhanced/nconvert.py b/image_occlusion_enhanced/nconvert.py
deleted file mode 100644
index 837f53d3..00000000
--- a/image_occlusion_enhanced/nconvert.py
+++ /dev/null
@@ -1,262 +0,0 @@
-# -*- coding: utf-8 -*-
-####################################################
-## ##
-## Image Occlusion Enhanced ##
-## ##
-## Copyright (c) Glutanimate 2016-2017 ##
-## (https://github.com/Glutanimate) ##
-## ##
-####################################################
-
-"""
-Makes older IO notes editable.
-"""
-
-import logging, sys
-
-from PyQt4.QtCore import SIGNAL
-from PyQt4.QtGui import QKeySequence
-from anki.hooks import addHook
-from aqt.utils import tooltip
-
-from xml.dom import minidom
-
-from config import *
-from dialogs import ioAskUser
-from utils import img2path, fname2img
-
-class ImgOccNoteConverter(object):
- def __init__(self, browser):
- self.browser = browser
- self.occl_id_last = None
- loadConfig(self)
-
- def convertNotes(self, nids):
- """Main note conversion method"""
- nids_by_nr = {}
- skipped = 0
- (io_nids, filtered) = self.filterSelected(nids)
- for nid in io_nids:
- note = mw.col.getNote(nid)
- (uniq_id, note_nr) = self.getDataFromNamingScheme(note)
- if uniq_id == False:
- logging.debug("Skipping note that couldn't be parsed: %s", nid)
- skipped += 1
- continue
- occl_tp = self.getOcclTypeAndNodes(note)
- occl_id = uniq_id + '-' + occl_tp
- if occl_id == self.occl_id_last:
- logging.debug("Skipping note that we've just converted: %s", nid)
- continue
- self.occl_id_last = occl_id
- for nid in self.findByNoteId(uniq_id):
- note = mw.col.getNote(nid)
- (uniq_id, note_nr) = self.getDataFromNamingScheme(note)
- if uniq_id == False:
- logging.debug("Skipping note that couldn't be parsed: %s", nid)
- skipped += 1
- continue
- nids_by_nr[int(note_nr)] = nid
- self.idAndCorrelateNotes(nids_by_nr, occl_id)
- converted = len(io_nids)
- tooltip("%i notes updated, %i skipped"
- % (converted - skipped, filtered + skipped))
-
- def filterSelected(self, nids):
- """Filters out notes with the wrong note type and those that are
- valid already"""
- io_nids = []
- filtered = 0
- for nid in nids:
- note = mw.col.getNote(nid)
- if note.model() != self.model:
- logging.debug("Skipping note with wrong note type: %s", nid)
- filtered +=1
- continue
- elif note[self.ioflds['id']]:
- logging.debug("Skipping IO note that is already editable: %s", nid)
- filtered +=1
- continue
- elif not note[self.ioflds['om']]:
- logging.debug("Skipping IO note without original SVG mask: %s", nid)
- filtered +=1
- continue
- logging.debug("Found IO note in need of update: %s", nid)
- io_nids.append(nid)
- return (io_nids, filtered)
-
- def findByNoteId(self, note_id):
- """Search collection for notes with given ID in their omask paths"""
- # need to use omask path because Note ID field is not yet set
- query = "'%s':'*%s*'" % ( self.ioflds['om'], note_id )
- logging.debug("query: %s", query)
- res = mw.col.findNotes(query)
- return res
-
- def getDataFromNamingScheme(self, note):
- """Get unique ID and note nr from qmask path"""
- qmask = note[self.ioflds['qm']]
- path = img2path(qmask, True)
- if not path:
- return (False, None)
- grps = path.split('_')
- try:
- if len(grps) == 2:
- logging.debug("Extracting data using IO 2.0 naming scheme")
- uniq_id = grps[0]
- note_nr = path.split(' ')[1].split('.')[0]
- else:
- logging.debug("Extracting data using IO Enhanced naming scheme")
- grps = path.split('-')
- uniq_id = grps[0]
- note_nr = int(grps[2]) - 1
- return (uniq_id, note_nr)
- except IndexError:
- return (False, None)
-
-
- def idAndCorrelateNotes(self, nids_by_nr, occl_id):
- """Update Note ID fields and omasks of all occlusion session siblings"""
- logging.debug("occl_id %s", occl_id)
- logging.debug("nids_by_nr %s", nids_by_nr)
- logging.debug("mnode_idxs %s", self.mnode_idxs)
-
- for nr in sorted(nids_by_nr.keys()):
- try:
- midx = self.mnode_idxs[nr]
- except IndexError:
- continue
- nid = nids_by_nr[nr]
- note = mw.col.getNote(nid)
- new_mnode_id = occl_id + '-' + str(nr+1)
- self.mnode.childNodes[midx].setAttribute("id", new_mnode_id)
- note[self.ioflds['id']] = new_mnode_id
- note.flush()
- logging.debug("Adding ID for note nr %s", nr)
- logging.debug("midx %s", midx)
- logging.debug("nid %s", nid)
- logging.debug("note %s", note)
- logging.debug("new_mnode_id %s", new_mnode_id)
-
- new_svg = self.svg_node.toxml()
- omask_path = self._saveMask(new_svg, occl_id, "O")
- logging.debug("omask_path %s", omask_path)
-
- for nid in nids_by_nr.values():
- note = mw.col.getNote(nid)
- note[self.ioflds['om']] = fname2img(omask_path)
- note.addTag(".io-converted")
- note.flush()
- logging.debug("Setting om and tag for nid %s", nid)
-
- def getOcclTypeAndNodes(self, note):
- """Determine oclusion type and svg mask nodes"""
- nr_of_masks = {}
- mnode_idxs = {}
- svg_mlayer = {}
- for i in ["qm", "om"]: # om second, so that end vars are correct
- svg_file = img2path(note[self.ioflds[i]], True)
- svg_node= self.readSvg(svg_file)
- svg_mlayer = self.layerNodesFrom(svg_node)[-1] # topmost layer
- mnode_idxs = self.getMaskNodes(svg_mlayer)
- nr_of_masks[i] = len(mnode_idxs)
- # decide on occl_tp based on nr of mask nodes in omask vs qmask
- if nr_of_masks["om"] != nr_of_masks["qm"]:
- occl_tp = "oa"
- else:
- occl_tp = "ao"
- self.svg_node = svg_node
- self.mnode = svg_mlayer
- self.mnode_idxs = mnode_idxs
- return occl_tp
-
- def readSvg(self, svg_file):
- """Read and fix malformatted IO 2.0 SVGs"""
- svg_doc = minidom.parse(svg_file)
- # ugly workaround for wrong namespace in older IO notes:
- svg_string = svg_doc.toxml().replace('ns0:', '').replace(':ns0','')
- svg_string = unicode(svg_string)
- svg_doc = minidom.parseString(svg_string.encode('utf-8'))
- svg_node = svg_doc.documentElement
- return svg_node
-
- def getMaskNodes(self, mlayer):
- """Find mask nodes in masks layer"""
- mnode_indexes = []
- for i, node in enumerate(mlayer.childNodes):
- if (node.nodeType == node.ELEMENT_NODE) and (node.nodeName != 'title'):
- mnode_indexes.append(i)
- return mnode_indexes
-
- def layerNodesFrom(self, svg_node):
- """Get layer nodes (topmost group nodes below the SVG node)"""
- assert (svg_node.nodeType == svg_node.ELEMENT_NODE)
- assert (svg_node.nodeName == 'svg')
- layer_nodes = [node for node in svg_node.childNodes
- if node.nodeType == node.ELEMENT_NODE]
- assert (len(layer_nodes) >= 1)
- # last, i.e. top-most element, needs to be a layer:
- assert (layer_nodes[-1].nodeName == 'g')
- return layer_nodes
-
- def _saveMask(self, mask, note_id, mtype):
- """Write mask to file in media collection"""
- logging.debug("!saving %s, %s", note_id, mtype)
- mask_path = '%s-%s.svg' % (note_id, mtype)
- mask_file = open(mask_path, 'w')
- mask_file.write(mask.encode('utf-8'))
- mask_file.close()
- return mask_path
-
-def onIoConvert(self):
- """Launch initial dialog, set up checkpoint, invoke converter"""
- mw = self.mw
- selected = self.selectedNotes()
- if not selected:
- tooltip("No cards selected.", period=2000)
- return
- question = u"""\
- This is a purely experimental feature that is meant to update older \
- IO notes to be compatible with the new editing feature-set in IO Enhanced. \
- Clicking on 'Yes' below will prompt the add-on to go through all selected \
- notes and change their Note ID and mask files in a way that should make it \
- possible to edit them in the future. \
-
Please note that this will only work for notes \
- that have already been switched to the Image Occlusion Enhanced note type.\
- If you are coming from IO 2.0 or an older version of IO Enhanced you will \
- first have to switch the note type of your notes manually by going to Edit → \
- Change Note Type.
\
- WARNING: There is no guarantee that this feature will actually succeed in \
- updating your notes properly. To convert legacy notes the add-on will have to \
- make a few assumptions which in some rare instances might turn out to be wrong \
- and lead to broken notes. Notes that can't be parsed for the information needed \
- to convert into an editable state (e.g. a valid "Original Mask" field) will usually \
- be skipped by the add-on, but there might be some corner cases where that won't work. \
-
A checkpoint will be set to revert to if needed, \
- but even with that safety measure in place you should still only use this \
- function if you know what you are doing.\
-
Continue anyway?
(Depending on the number of notes this might \
- take a while)
- """
- ret = ioAskUser(question, "Please confirm action", self, defaultno=True)
- if not ret:
- return False
- mw.progress.start()
- mw.checkpoint("Image Occlusion Note Conversions")
- self.model.beginReset()
- conv = ImgOccNoteConverter(self)
- conv.convertNotes(selected)
- self.model.endReset()
- mw.col.reset()
- mw.reset()
- mw.progress.finish()
-
-# Set up menus and hooks
-
-def setupMenu(self):
- menu = self.form.menuEdit
- menu.addSeparator()
- a = menu.addAction('Convert to Editable IO &Enhanced Notes')
- self.connect(a, SIGNAL("triggered()"), lambda b=self: onIoConvert(b))
-
-addHook("browser.setupMenus", setupMenu)
diff --git a/image_occlusion_enhanced/ngen.py b/image_occlusion_enhanced/ngen.py
deleted file mode 100644
index b04bbf1c..00000000
--- a/image_occlusion_enhanced/ngen.py
+++ /dev/null
@@ -1,469 +0,0 @@
-# -*- coding: utf-8 -*-
-####################################################
-## ##
-## Image Occlusion Enhanced ##
-## ##
-## Copyright (c) Glutanimate 2016-2017 ##
-## (https://github.com/Glutanimate) ##
-## ##
-## Based on Simple Picture Occlusion ##
-## Copyright (c) 2013 SteveAW ##
-## (https://github.com/steveaw) ##
-## ##
-####################################################
-
-"""
-Generates the actual IO notes and writes them to
-the collection.
-"""
-
-import os
-import logging, sys
-
-from aqt.qt import *
-from aqt import mw
-from aqt.utils import tooltip
-from anki.notes import Note
-
-from xml.dom import minidom
-from uuid import uuid
-
-from dialogs import ioHelp, ioAskUser
-from utils import fname2img
-from config import *
-
-# Explanation of some of the variables:
-#
-# nid: Note ID set by Anki
-# note_id: Image Occlusion Note ID set as the first field of each IO note
-# uniq_id: Unique sequence of random characters. First part of the note_id
-# occl_tp: Two-letter code that signifies occlusion type. Second part of
-# the note_id
-# occl_id: Combination of uniq_id + occl_tp - unique identifier shared
-# by all notes created in one IO session
-# note_nr: Third part of the note_id
-
-def genByKey(key, old_occl_tp=None):
- """Get note generator based on occl_tp/user input"""
- if key in ["Don't Change"]:
- return genByKey(old_occl_tp, None)
- elif key in ["ao", "Hide All, Reveal One"]:
- return IoGenHideAllRevealOne
- elif key in ["oa", "Hide One, Reveal All"]:
- return IoGenHideOneRevealAll
- else:
- return IoGenHideAllRevealOne
-
-class ImgOccNoteGenerator(object):
- """Generic note generator object"""
-
- stripattr = ['opacity', 'stroke-opacity', 'fill-opacity']
-
- def __init__(self, ed, svg, image_path, opref, tags, fields, did):
- self.ed = ed
- self.new_svg = svg
- self.image_path = image_path
- self.opref = opref
- self.tags = tags
- self.fields = fields
- self.did = did
- self.qfill = '#' + mw.col.conf['imgocc']['qfill']
- loadConfig(self)
-
- def generateNotes(self):
- """Generate new notes"""
- state = "default"
- self.uniq_id = str(uuid.uuid4()).replace("-","")
- self.occl_id = '%s-%s' % (self.uniq_id, self.occl_tp)
-
- ( svg_node, layer_node ) = self._getMnodesAndSetIds()
- if not self.mnode_ids:
- tooltip("No cards to generate.
\
- Are you sure you set your masks correctly?")
- return False
-
- self.new_svg = svg_node.toxml() # write changes to svg
- omask_path = self._saveMask(self.new_svg, self.occl_id, "O")
- qmasks = self._generateMaskSVGsFor("Q")
- amasks = self._generateMaskSVGsFor("A")
- image_path = mw.col.media.addFile(self.image_path)
- img = fname2img(image_path)
-
- mw.checkpoint("Adding Image Occlusion Cards")
- for nr, idx in enumerate(self.mnode_indexes):
- note_id = self.mnode_ids[idx]
- self._saveMaskAndReturnNote(omask_path, qmasks[nr], amasks[nr],
- img, note_id)
- tooltip("%s %s added" % self._cardS(len(qmasks)), parent=None)
- return state
-
- def updateNotes(self):
- """Update existing notes"""
- state = "default"
- self.uniq_id = self.opref['uniq_id']
- self.occl_id = '%s-%s' % (self.uniq_id, self.occl_tp)
- omask_path = None
-
- self._findAllNotes()
- ( svg_node, mlayer_node ) = self._getMnodesAndSetIds(True)
- if not self.mnode_ids:
- tooltip("No shapes left. You can't delete all cards.
\
- Are you sure you set your masks correctly?")
- return False
- mw.checkpoint("Editing Image Occlusion Cards")
- ret = self._deleteAndIdNotes(mlayer_node)
- if not ret:
- # confirmation window rejected
- return False
- else:
- (del_count, new_count) = ret
-
- self.new_svg = svg_node.toxml() # write changes to svg
- old_svg = self._getOriginalSvg() # load original svg
- if self.new_svg != old_svg or self.occl_tp != self.opref["occl_tp"]:
- # updated masks or occlusion type
- omask_path = self._saveMask(self.new_svg, self.occl_id, "O")
- qmasks = self._generateMaskSVGsFor("Q")
- amasks = self._generateMaskSVGsFor("A")
- state = "reset"
-
- image_path = mw.col.media.addFile(self.image_path)
- img = fname2img(image_path)
-
- logging.debug("mnode_indexes %s", self.mnode_indexes)
- for nr, idx in enumerate(self.mnode_indexes):
- logging.debug("=====================")
- logging.debug("nr %s", nr)
- logging.debug("idx %s", idx)
- note_id = self.mnode_ids[idx]
- logging.debug("note_id %s", note_id)
- logging.debug("self.nids %s", self.nids)
- nid = self.nids[note_id]
- logging.debug("nid %s", nid)
- if omask_path:
- self._saveMaskAndReturnNote(omask_path, qmasks[nr], amasks[nr],
- img, note_id, nid)
- else:
- self._saveMaskAndReturnNote(None, None, None,
- img, note_id, nid)
- self._showUpdateTooltip(del_count, new_count)
- return state
-
- def _cardS(self, cnt):
- s = "card"
- if cnt > 1 or cnt == 0:
- s = "cards"
- return (cnt, s)
-
- def _showUpdateTooltip(self, del_count, new_count):
- upd_count = max(0, len(self.mnode_indexes) - del_count - new_count)
- ttip = "%s old %s edited in place" % self._cardS(upd_count)
- if del_count > 0:
- ttip += "
%s existing %s deleted" % self._cardS(del_count)
- if new_count > 0:
- ttip += "
%s new %s created" % self._cardS(new_count)
- tooltip(ttip, parent=self.ed.parentWindow)
-
- def _getOriginalSvg(self):
- """Returns original SVG as a string"""
- mask_doc = minidom.parse(self.opref["omask"])
- svg_node = mask_doc.documentElement
- return svg_node.toxml()
-
- def _layerNodesFrom(self, svg_node):
- """Get layer nodes (topmost group nodes below the SVG node)"""
- assert (svg_node.nodeType == svg_node.ELEMENT_NODE)
- assert (svg_node.nodeName == 'svg')
- layer_nodes = [node for node in svg_node.childNodes
- if node.nodeType == node.ELEMENT_NODE]
- assert (len(layer_nodes) >= 1)
- # last, i.e. top-most element, needs to be a layer:
- assert (layer_nodes[-1].nodeName == 'g')
- return layer_nodes
-
- def _getMnodesAndSetIds(self, edit=False):
- """Find mask nodes in masks layer and read/set node IDs"""
- self.mnode_indexes = []
- self.mnode_ids = {}
- mask_doc = minidom.parseString(self.new_svg.encode('utf-8'))
- svg_node = mask_doc.documentElement
- cheight = float(svg_node.attributes["height"].value)
- cwidth = float(svg_node.attributes["width"].value)
- carea = cheight * cwidth
- layer_nodes = self._layerNodesFrom(svg_node)
- mlayer_node = layer_nodes[-1] # treat topmost layer as masks layer
-
- shift = 0
- for i, mnode in enumerate(mlayer_node.childNodes):
- # minidom doesn't offer a childElements method and childNodes
- # also returns whitespace found in the mlayer_node as a child node.
- # For that reason we use self.mnode_indexes to register all
- # indexes of mlayer_node children that contain actual elements,
- # i.e. mask nodes
- if (mnode.nodeType == mnode.ELEMENT_NODE) and (mnode.nodeName != 'title'):
- i -= shift
- if not edit and mnode.nodeName == "rect":
- # remove microscopical shapes (usually accidentally drawn)
- h_attr = mnode.attributes.get("height", 0)
- w_attr = mnode.attributes.get("width", 0)
- height = h_attr if not h_attr else float(mnode.attributes["height"].value)
- width = w_attr if not w_attr else float(mnode.attributes["width"].value)
- if not height or not width or 100*(height*width)/carea <= 0.01:
- mlayer_node.removeChild(mnode)
- shift += 1
- continue
- self.mnode_indexes.append(i)
- self._removeAttribsRecursively(mnode, self.stripattr)
- if mnode.nodeName == "g":
- # remove IDs of grouped shapes to prevent duplicates down the line
- for node in mnode.childNodes:
- self._removeAttribsRecursively(node, ["id"])
- if not edit:
- self.mnode_ids[i] = "%s-%i" %(self.occl_id, len(self.mnode_indexes))
- mnode.setAttribute("id", self.mnode_ids[i])
- else:
- self.mnode_ids[i] = mnode.attributes["id"].value
-
- return (svg_node, mlayer_node)
-
- def _findByNoteId(self, note_id):
- """Search collection for notes with given ID"""
- query = "'%s':'%s*'" % ( self.ioflds['id'], note_id )
- logging.debug("query %s", query)
- res = mw.col.findNotes(query)
- return res
-
- def _findAllNotes(self):
- """Get matching nids by ID"""
- old_occl_id = '%s-%s' % (self.uniq_id, self.opref["occl_tp"])
- res = self._findByNoteId(old_occl_id)
- self.nids = {}
- for nid in res:
- note_id = mw.col.getNote(nid)[self.ioflds['id']]
- self.nids[note_id] = nid
- logging.debug('--------------------')
- logging.debug("res %s", res)
- logging.debug("nids %s", self.nids)
-
- def _deleteAndIdNotes(self, mlayer_node):
- """
- Determine which mask nodes have been deleted or newly created and, depending
- on which, either delete their respective notes or ID them in correspondence
- with the numbering of older nodes
- """
- uniq_id = self.opref['uniq_id']
- mnode_ids = self.mnode_ids
- nids = self.nids
-
- # look for missing shapes by note_id
- valid_mnode_note_ids = filter (lambda x:x.startswith(uniq_id), mnode_ids.values())
- valid_nid_note_ids = filter (lambda x:x.startswith(uniq_id), nids.keys())
- # filter out notes that have already been deleted manually
- exstg_mnode_note_ids = [x for x in valid_mnode_note_ids if x in valid_nid_note_ids]
- exstg_mnode_note_nrs = sorted([int(i.split('-')[-1]) for i in exstg_mnode_note_ids])
- # determine available nrs available for note numbering
- if not exstg_mnode_note_nrs:
- # only the case if the user deletes all existing shapes
- max_mnode_note_nr = 0
- full_range = None
- available_nrs = None
- else:
- max_mnode_note_nr = int(exstg_mnode_note_nrs[-1])
- full_range = range(1, max_mnode_note_nr+1)
- available_nrs = set(full_range) - set(exstg_mnode_note_nrs)
- available_nrs = sorted(list(available_nrs))
-
- # compare note_ids as present in note collection with masks on svg
- deleted_note_ids = set(valid_nid_note_ids) - set(valid_mnode_note_ids)
- deleted_note_ids = sorted(list(deleted_note_ids))
- del_count = len(deleted_note_ids)
- # set notes of missing masks on svg to be deleted
- deleted_nids = [nids[x] for x in deleted_note_ids]
-
- logging.debug('--------------------')
- logging.debug("valid_mnode_note_ids %s", valid_mnode_note_ids)
- logging.debug("exstg_mnode_note_nrs %s", exstg_mnode_note_nrs)
- logging.debug("max_mnode_note_nr %s", max_mnode_note_nr)
- logging.debug("full_range %s", full_range)
- logging.debug("available_nrs %s", available_nrs)
- logging.debug('--------------------')
- logging.debug("valid_nid_note_ids %s", valid_nid_note_ids)
- logging.debug("deleted_note_ids %s", deleted_note_ids)
- logging.debug("deleted_nids %s", deleted_nids)
-
- # add note_id to missing shapes
- note_nr_max = max_mnode_note_nr
- new_count = 0
- for nr, idx in enumerate(self.mnode_indexes):
- mnode_id = mnode_ids[idx]
- new_mnode_id = None
- mnode = mlayer_node.childNodes[idx]
- if mnode_id not in exstg_mnode_note_ids:
- if available_nrs:
- # use gap in note_id numbering
- note_nr = available_nrs.pop(0)
- else:
- # increment maximum note_id number
- note_nr_max = note_nr_max +1
- note_nr = note_nr_max
- new_mnode_id = self.occl_id + '-' + str(note_nr)
- new_count += 1
- nids[new_mnode_id] = None
- else:
- # update occlusion type
- mnode_id_nr = mnode_id.split('-')[-1]
- new_mnode_id = self.occl_id + '-' + mnode_id_nr
- nids[new_mnode_id] = nids.pop(mnode_id)
- if new_mnode_id:
- mnode.setAttribute("id", new_mnode_id)
- self.mnode_ids[idx] = new_mnode_id
-
- logging.debug("=====================")
- logging.debug("nr %s", nr)
- logging.debug("idx %s", idx)
- logging.debug("mnode_id %s", mnode_id)
- logging.debug("available_nrs %s", available_nrs)
- logging.debug("note_nr_max %s", note_nr_max)
- logging.debug("new_mnode_id %s", new_mnode_id)
-
- logging.debug('--------------------')
- logging.debug("edited nids %s", nids)
- logging.debug("edited self.mnode_ids %s", self.mnode_ids)
-
- if del_count or new_count:
- q = "This will delete %i card(s) and \
- create %i new one(s).\
- Please note that this action is irreversible.
\
- Would you still like to proceed?" % (del_count, new_count)
- if not ioAskUser(q, title="Please confirm action",
- parent=mw.ImgOccEdit, help="edit"):
- return False
-
- if deleted_nids:
- mw.col.remNotes(deleted_nids)
- return (del_count, new_count)
-
- def _generateMaskSVGsFor(self, side):
- """Generate a mask for each mask node"""
- masks = [self._createMask(side, node_index) for node_index in self.mnode_indexes]
- return masks
-
- def _createMask(self, side, mask_node_index):
- """Call occl_tp-specific mask generator"""
- mask_doc = minidom.parseString(self.new_svg.encode('utf-8'))
- svg_node = mask_doc.documentElement
- layer_nodes = self._layerNodesFrom(svg_node)
- mlayer_node = layer_nodes[-1] # treat topmost layer as masks layer
- #This method gets implemented differently by subclasses
- self._createMaskAtLayernode(side, mask_node_index, mlayer_node)
- return svg_node.toxml()
-
- def _createMaskAtLayernode(self, mask_node_index, mlayer_node):
- raise NotImplementedError
-
- def _setQuestionAttribs(self, node):
- """Set question node color and class"""
- if (node.nodeType == node.ELEMENT_NODE and node.tagName != "text"):
- # set question class
- node.setAttribute("class", "qshape")
- if node.hasAttribute("fill"):
- # set question color
- node.setAttribute("fill", self.qfill)
- map(self._setQuestionAttribs, node.childNodes)
-
- def _removeAttribsRecursively(self, node, attrs):
- """Remove provided attributes recursively from node and children"""
- if (node.nodeType == node.ELEMENT_NODE):
- for i in attrs:
- if node.hasAttribute(i):
- node.removeAttribute(i)
- for i in node.childNodes:
- self._removeAttribsRecursively(i, attrs)
-
- def _saveMask(self, mask, note_id, mtype):
- """Write mask to file in media collection"""
- logging.debug("!saving %s, %s", note_id, mtype)
- # media collection is the working directory:
- mask_path = '%s-%s.svg' % (note_id, mtype)
- mask_file = open(mask_path, 'w')
- mask_file.write(mask.encode('utf8'))
- mask_file.close()
- return mask_path
-
- def removeBlanks(self, node):
- for x in node.childNodes:
- if x.nodeType == node.TEXT_NODE:
- if x.nodeValue:
- x.nodeValue = x.nodeValue.strip()
- elif x.nodeType == node.ELEMENT_NODE:
- self.removeBlanks(x)
-
- def _saveMaskAndReturnNote(self, omask_path, qmask, amask,
- img, note_id, nid=None):
- """Write actual note for given qmask and amask"""
- fields = self.fields
- model = self.model
- mflds = self.mflds
- fields[self.ioflds['im']] = img
- if omask_path:
- # Occlusions updated
- qmask_path = self._saveMask(qmask, note_id, "Q")
- amask_path = self._saveMask(amask, note_id, "A")
- fields[self.ioflds['qm']] = fname2img(qmask_path)
- fields[self.ioflds['am']] = fname2img(amask_path)
- fields[self.ioflds['om']] = fname2img(omask_path)
- fields[self.ioflds['id']] = note_id
-
- self.model['did'] = self.did
- if nid:
- note = mw.col.getNote(nid)
- else:
- note = Note(mw.col, model)
-
- # add fields to note
- note.tags = self.tags
- for i in mflds:
- fname = i["name"]
- if fname in fields:
- # only update fields that have been modified
- note[fname] = fields[fname]
-
- if nid:
- note.flush()
- logging.debug("!noteflush %s", note)
- else:
- mw.col.addNote(note)
- logging.debug("!notecreate %s", note)
-
-
-# Different generator subclasses for different occlusion types:
-
-class IoGenHideAllRevealOne(ImgOccNoteGenerator):
- """Q: All hidden, A: One revealed ('nonoverlapping')"""
- occl_tp = "ao"
- def __init__(self, ed, svg, image_path, opref, tags, fields, did):
- ImgOccNoteGenerator.__init__(self, ed, svg, image_path,
- opref, tags, fields, did)
-
- def _createMaskAtLayernode(self, side, mask_node_index, mlayer_node):
- mask_node = mlayer_node.childNodes[mask_node_index]
- if side == "Q":
- self._setQuestionAttribs(mask_node)
- elif side == "A":
- mlayer_node.removeChild(mask_node)
-
-class IoGenHideOneRevealAll(ImgOccNoteGenerator):
- """Q: One hidden, A: All revealed ('overlapping')"""
- occl_tp = "oa"
- def __init__(self, ed, svg, image_path, opref, tags, fields, did):
- ImgOccNoteGenerator.__init__(self, ed, svg, image_path,
- opref, tags, fields, did)
-
- def _createMaskAtLayernode(self, side, mask_node_index, mlayer_node):
- for i in reversed(self.mnode_indexes):
- mask_node = mlayer_node.childNodes[i]
- if i == mask_node_index and side == "Q":
- self._setQuestionAttribs(mask_node)
- mask_node.setAttribute("class", "qshape")
- else:
- mlayer_node.removeChild(mask_node)
diff --git a/image_occlusion_enhanced/options.py b/image_occlusion_enhanced/options.py
deleted file mode 100644
index 143b009e..00000000
--- a/image_occlusion_enhanced/options.py
+++ /dev/null
@@ -1,377 +0,0 @@
-# -*- coding: utf-8 -*-
-####################################################
-## ##
-## Image Occlusion Enhanced ##
-## ##
-## Copyright (c) Glutanimate 2016-2017 ##
-## (https://github.com/Glutanimate) ##
-## ##
-## Based on Image Occlusion 2.0 ##
-## Copyright (c) 2012-2015 tmbb ##
-## (https://github.com/tmbb) ##
-## ##
-####################################################
-
-"""
-Main options dialog
-"""
-
-import logging, sys
-
-from PyQt4 import QtCore, QtGui
-from aqt.qt import *
-from aqt.utils import showInfo
-
-from aqt import mw
-from anki.errors import AnkiError
-
-from config import *
-
-class GrabKey(QDialog):
- """
- Grab the key combination to paste the resized image
-
- Largely based on ImageResizer by searene
- (https://github.com/searene/Anki-Addons)
- """
-
- def __init__(self, parent):
- QDialog.__init__(self, parent=parent)
- self.parent = parent
- self.key = parent.hotkey
- # self.active is used to trace whether there's any key held now
- self.active = 0
- self.ctrl = False
- self.alt = False
- self.shift = False
- self.extra = None
- self.setupUI()
-
- def setupUI(self):
- mainLayout = QVBoxLayout()
- self.setLayout(mainLayout)
-
- label = QLabel('Please press the new key combination')
- mainLayout.addWidget(label)
-
- self.setWindowTitle('Grab key combination')
-
- def keyPressEvent(self, evt):
- self.active += 1
- if evt.key() >0 and evt.key() < 127:
- self.extra = chr(evt.key())
- elif evt.key() == Qt.Key_Control:
- self.ctrl = True
- elif evt.key() == Qt.Key_Alt:
- self.alt = True
- elif evt.key() == Qt.Key_Shift:
- self.shift = True
-
- def keyReleaseEvent(self, evt):
- self.active -= 1
-
- if self.active != 0:
- return
- if not (self.shift or self.ctrl or self.alt):
- showInfo("Please use at least one keyboard "
- "modifier (Ctrl, Alt, Shift)")
- return
- if (self.shift and not (self.ctrl or self.alt)):
- showInfo("Shift needs to be combined with at "
- "least one other modifier (Ctrl, Alt)")
- return
- if not self.extra:
- showInfo("Please press at least one key "
- "that is not a keyboard modifier (not Ctrl/Alt/Shift)")
- return
-
- combo = []
- if self.ctrl:
- combo.append("Ctrl")
- if self.shift:
- combo.append("Shift")
- if self.alt:
- combo.append("Alt")
- combo.append(self.extra)
-
- self.parent.updateHotkey("+".join(combo))
- self.close()
-
-
-class ImgOccOpts(QDialog):
- """Main Image Occlusion Options dialog"""
- def __init__(self, mw):
- QDialog.__init__(self, parent=mw)
- loadConfig(self)
- self.ofill = self.sconf['ofill']
- self.qfill = self.sconf['qfill']
- self.scol = self.sconf['scol']
- self.swidth = self.sconf['swidth']
- self.font = self.sconf['font']
- self.fsize = self.sconf['fsize']
- self.hotkey = self.lconf["hotkey"]
- self.setupUi()
- self.setupValues(self.sconf)
-
- def setupValues(self, config):
- """Set up widget data based on provided config dict"""
- self.updateHotkey()
- self.changeButtonColor(self.ofill_btn, config['ofill'])
- self.changeButtonColor(self.qfill_btn, config['qfill'])
- self.changeButtonColor(self.scol_btn, config['scol'])
- self.swidth_sel.setValue(int(config['swidth']))
- self.fsize_sel.setValue(int(config['fsize']))
- self.swidth_sel.setValue(int(config['swidth']))
- self.font_sel.setCurrentFont(QFont(config['font']))
- self.skipped.setText(','.join(config["skip"]))
-
- def setupUi(self):
- """Set up widgets and layouts"""
-
- # Top section
- qfill_label = QLabel('Question mask')
- ofill_label = QLabel('Other masks')
- scol_label = QLabel('Lines')
- colors_heading = QLabel("Colors")
- fields_heading = QLabel("Custom Field Names")
- other_heading = QLabel("Other Editor Settings")
-
- self.qfill_btn = QPushButton()
- self.ofill_btn = QPushButton()
- self.scol_btn = QPushButton()
- self.qfill_btn.connect(self.qfill_btn, SIGNAL("clicked()"),
- lambda a="qfill", b=self.qfill_btn: self.getNewColor(a, b))
- self.ofill_btn.connect(self.ofill_btn, SIGNAL("clicked()"),
- lambda a="ofill", b=self.ofill_btn: self.getNewColor(a, b))
- self.scol_btn.connect(self.scol_btn, SIGNAL("clicked()"),
- lambda a="scol", b=self.scol_btn: self.getNewColor(a, b))
-
- swidth_label = QLabel("Line width")
- font_label = QLabel("Label font")
- fsize_label = QLabel("Label size")
-
- self.swidth_sel = QSpinBox()
- self.swidth_sel.setMinimum(0)
- self.swidth_sel.setMaximum(20)
- self.font_sel = QFontComboBox()
- self.fsize_sel = QSpinBox()
- self.fsize_sel.setMinimum(5)
- self.fsize_sel.setMaximum(300)
-
- # Horizontal lines
- rule1 = self.create_horizontal_rule()
- rule2 = self.create_horizontal_rule()
-
- # Bottom section and grid assignment
-
- fields_text = ("Changing any of the entries below will rename "
- "the corresponding default field of the IO Enhanced note type. "
- "This is the only way you can rename any of the default fields. "
- "
Renaming these fields through Anki's regular dialogs "
- "will cause the add-on to fail. So please don't do that.")
-
- fields_description = QLabel(fields_text)
- fields_description.setWordWrap(True)
-
- grid = QtGui.QGridLayout()
- grid.setSpacing(10)
-
- grid.addWidget(colors_heading, 0, 0, 1, 3)
- grid.addWidget(qfill_label, 1, 0, 1, 1)
- grid.addWidget(self.qfill_btn, 1, 1, 1, 2)
- grid.addWidget(ofill_label, 2, 0, 1, 1)
- grid.addWidget(self.ofill_btn, 2, 1, 1, 2)
- grid.addWidget(scol_label, 3, 0, 1, 1)
- grid.addWidget(self.scol_btn, 3, 1, 1, 2)
-
- grid.addWidget(other_heading, 0, 3, 1, 3)
- grid.addWidget(swidth_label, 1, 3, 1, 1)
- grid.addWidget(self.swidth_sel, 1, 4, 1, 2)
- grid.addWidget(font_label, 2, 3, 1, 1)
- grid.addWidget(self.font_sel, 2, 4, 1, 2)
- grid.addWidget(fsize_label, 3, 3, 1, 1)
- grid.addWidget(self.fsize_sel, 3, 4, 1, 2)
-
- grid.addWidget(rule1, 4, 0, 1, 6)
- grid.addWidget(fields_heading, 5, 0, 1, 6)
- grid.addWidget(fields_description, 6, 0, 1, 6)
-
- # Field name entries
- row = 7
- clm = 0
- self.lnedit = {}
- for key in IO_FLDS_IDS:
- if row == 13: # switch to right columns
- clm = 3
- row = 7
- default_name = self.sconf_dflt['flds'][key]
- current_name = self.sconf['flds'][key]
- l = QLabel(default_name)
- l.setTextInteractionFlags(Qt.TextSelectableByMouse)
- t = QLineEdit()
- t.setText(current_name)
- grid.addWidget(l, row, clm, 1, 2)
- grid.addWidget(t, row, clm+1, 1, 2)
- self.lnedit[key] = t
- row = row+1
-
- # Misc settings
- misc_heading = QLabel("Miscellaneous Settings")
-
- # Skipped fields:
- skipped_description = QLabel("Comma-separated list of " \
- "fields to hide in Editing mode (in order to preserve manual edits):")
- self.skipped = QLineEdit()
-
- # Hotkey:
- key_grab_label = QLabel('Invoke IO with the following hotkey:')
- self.key_grabbed = QLabel('')
- key_grab_btn = QPushButton('Change hotkey', self)
- key_grab_btn.clicked.connect(self.showGrabKey)
-
- grid.addWidget(rule2, row+1, 0, 1, 6)
- grid.addWidget(misc_heading, row+2, 0, 1, 6)
- grid.addWidget(skipped_description, row+3, 0, 1, 6)
- grid.addWidget(self.skipped, row+4, 0, 1, 6)
- grid.addWidget(key_grab_label, row+5, 0, 1, 2)
- grid.addWidget(self.key_grabbed, row+5, 2, 1, 1)
- grid.addWidget(key_grab_btn, row+5, 3, 1, 3)
-
- # Main button box
- button_box = QDialogButtonBox(QDialogButtonBox.Ok
- | QDialogButtonBox.Cancel)
- defaults_btn = button_box.addButton("Restore &Defaults",
- QDialogButtonBox.ResetRole)
- self.connect(defaults_btn, SIGNAL("clicked()"), self.restoreDefaults)
- button_box.accepted.connect(self.onAccept)
- button_box.rejected.connect(self.onReject)
-
- # Main layout
- l_main = QVBoxLayout()
- l_main.addLayout(grid)
- l_main.addWidget(button_box)
- self.setLayout(l_main)
- self.setMinimumWidth(800)
- self.setMinimumHeight(640)
- self.setWindowTitle('Image Occlusion Enhanced Options')
-
- def create_horizontal_rule(self):
- """
- Returns a QFrame that is a sunken, horizontal rule.
- """
- frame = QtGui.QFrame()
- frame.setFrameShape(QtGui.QFrame.HLine)
- frame.setFrameShadow(QtGui.QFrame.Sunken)
- return frame
-
- def updateHotkey(self, combo=None):
- """Update hotkey label and attribute"""
- key = combo or self.hotkey
- label = u"{}".format(key)
- self.key_grabbed.setText(label)
- if combo:
- self.hotkey = combo
-
- def showGrabKey(self):
- """Invoke key grabber"""
- win = GrabKey(self)
- win.show()
-
- def getNewColor(self, clrvar, clrbtn):
- """Set color via color selection dialog"""
- dialog = QColorDialog()
- color = dialog.getColor()
- if color.isValid():
- # Remove the # sign from QColor.name():
- color = color.name()[1:]
- if clrvar == "qfill":
- self.qfill = color
- elif clrvar == "ofill":
- self.ofill = color
- elif clrvar == "scol":
- self.scol = color
- self.changeButtonColor(clrbtn, color)
-
- def changeButtonColor(self, button, color):
- """Generate color preview pixmap and place it on button"""
- pixmap = QPixmap(128,18)
- qcolour = QtGui.QColor(0, 0, 0)
- qcolour.setNamedColor('#' + color)
- pixmap.fill(qcolour)
- button.setIcon(QIcon(pixmap))
- button.setIconSize(QSize(128, 18))
-
- def restoreDefaults(self):
- """Restore colors and fields back to defaults"""
- self.hotkey = self.lconf_dflt["hotkey"]
- for key in self.lnedit.keys():
- self.lnedit[key].setText(IO_FLDS[key])
- self.lnedit[key].setModified(True)
- self.setupValues(self.sconf_dflt)
- self.ofill = self.sconf_dflt["ofill"]
- self.qfill = self.sconf_dflt["qfill"]
- self.scol = self.sconf_dflt["scol"]
-
- def renameFields(self):
- """Check for modified names and rename fields accordingly"""
- modified = False
- model = mw.col.models.byName(IO_MODEL_NAME)
- flds = model['flds']
- for key in self.lnedit.keys():
- if not self.lnedit[key].isModified():
- continue
- name = self.lnedit[key].text()
- oldname = mw.col.conf['imgocc']['flds'][key]
- if (name is None or not name.strip() or name == oldname):
- continue
- fnames = mw.col.models.fieldNames(model)
- if (name in fnames and oldname not in fnames):
- # case: imported cards, fields not corresponding to config
- mw.col.conf['imgocc']['flds'][key] = name
- modified = True
- continue
- idx = fnames.index(oldname)
- fld = flds[idx]
- if fld:
- # rename note type fields
- mw.col.models.renameField(model, fld, name)
- # update imgocc field-id <-> field-name assignment
- mw.col.conf['imgocc']['flds'][key] = name
- modified = True
- logging.debug("Renamed %s to %s", oldname, name)
- if modified:
- flds = model['flds']
-
- return (modified, flds)
-
- def onAccept(self):
- """Apply changes on OK button press"""
- modified = False
- try:
- (modified, flds) = self.renameFields()
- except AnkiError:
- print "Field rename action aborted"
- return
- if modified and hasattr(mw, "ImgOccEdit"):
- self.resetIoEditor(flds)
- mw.col.conf['imgocc']['ofill'] = self.ofill
- mw.col.conf['imgocc']['qfill'] = self.qfill
- mw.col.conf['imgocc']['scol'] = self.scol
- mw.col.conf['imgocc']['swidth'] = self.swidth_sel.value()
- mw.col.conf['imgocc']['fsize'] = self.fsize_sel.value()
- mw.col.conf['imgocc']['font'] = self.font_sel.currentFont().family()
- mw.col.conf['imgocc']['skip'] = self.skipped.text().split(',')
- mw.pm.profile["imgocc"]["hotkey"] = self.hotkey
- mw.col.setMod()
- self.close()
-
- def resetIoEditor(self, flds):
- """Reset existing instance of IO Editor"""
- dialog = mw.ImgOccEdit
- loadConfig(dialog)
- dialog.resetFields()
- dialog.setupFields(flds)
-
- def onReject(self):
- """Dismiss changes on Close button press"""
- self.close()
diff --git a/image_occlusion_enhanced/resources.py b/image_occlusion_enhanced/resources.py
deleted file mode 100644
index 29788b1a..00000000
--- a/image_occlusion_enhanced/resources.py
+++ /dev/null
@@ -1,272 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Resource object code
-#
-# Created: ter 12. Mar 10:47:51 2013
-# by: The Resource Compiler for PyQt (Qt v4.7.4)
-#
-# WARNING! All changes made in this file will be lost!
-
-from PyQt4 import QtCore
-
-qt_resource_data = "\
-\x00\x00\x06\xe5\
-\x89\
-\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
-\x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\
-\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\
-\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\
-\x79\x71\xc9\x65\x3c\x00\x00\x06\x87\x49\x44\x41\x54\x78\xda\xb4\
-\x57\x6b\x6c\x54\x45\x14\xfe\xe6\x3e\xf6\xee\x16\x4a\xa5\x40\x8b\
-\x85\x10\x20\x96\xa8\x21\x12\x51\x41\x8d\x31\x04\xf1\x81\x28\xe0\
-\x0f\x0c\x51\x13\x31\x1a\xa3\x7f\xc0\x18\x42\xe4\x11\x7f\xf8\x03\
-\x22\x18\x40\x13\x83\x31\x51\xc1\x28\x92\x18\x63\x42\x44\xa2\xf0\
-\x83\x5a\x11\x91\xda\x08\x22\x09\x14\x04\x5a\x4a\x68\xbb\x5d\xdb\
-\xdd\xed\x3e\xee\xee\xde\xf1\x9c\x99\xd9\xdd\x3e\x20\x14\x52\x6e\
-\x72\xb2\xb3\x73\xcf\xcc\x39\xe7\x9b\xef\x9c\x33\x57\x00\xb0\xb7\
-\x2e\xbf\xfb\xbd\x31\xa3\xac\x75\x81\xa4\x7f\x2c\xb7\xf2\x11\x80\
-\x45\x12\xef\x0b\x36\xbe\xbd\xe7\xd4\xbb\x0e\x4d\x85\x3d\xc7\x5a\
-\xe7\xda\x40\x55\x95\x40\x10\x90\x0f\xb7\xc8\x09\xc1\xc6\x2d\xa0\
-\xb7\x57\x82\x6d\xd2\xd4\x46\x76\x20\xc2\x2f\x73\x79\x20\x93\x01\
-\x26\xd5\x02\x85\x82\x24\x27\xe4\x88\x81\x21\x94\x71\x01\xdb\x16\
-\x68\xef\xd0\xb6\xcc\x13\x61\x07\x2c\x1e\xd9\x8e\x40\x32\x25\x11\
-\x8d\xe6\x51\x3f\x25\x8d\x20\x9f\x1b\x39\x28\xc8\xb8\xe5\xb8\x68\
-\x69\x8d\x90\x0d\x47\xd9\x82\xaf\xf6\xb6\x9c\xa2\x8e\x63\xb3\x27\
-\x01\x62\xd1\x3e\x24\xde\x6f\x05\xb2\xf4\x5f\x6a\xf7\xfb\x53\x43\
-\x18\x19\xf6\x43\x0b\x03\x5e\xe0\x01\xb1\x57\xa7\xc0\xad\xa8\x84\
-\x65\xdb\xa5\xd7\xca\x01\x0e\xd4\x21\x1c\x08\x79\xd2\xcf\x61\x56\
-\x08\xd8\x7c\x52\xbf\xe4\xc5\x8c\x58\xc1\x2c\xb0\xcd\x22\x5b\x0e\
-\x72\x44\x0c\x35\xcc\x0f\x51\x4a\xad\x5f\x33\x1b\x38\x4d\x7b\xdb\
-\x64\x87\xa5\x08\x6e\x09\x01\xdb\xd1\x7b\x48\xda\x39\xcb\x0e\xdb\
-\x5a\x91\x0d\xbd\x1c\xbc\x8e\x71\xf8\x43\xe9\x75\x63\x0e\x76\x59\
-\x9f\x2a\xc7\xec\xc1\x76\xad\x7e\xc6\x8d\x01\x36\xc4\x68\xf3\x9e\
-\x36\x6d\xa6\x90\x76\x86\x2e\x51\xec\x14\x6c\x90\x14\xe8\xf4\x11\
-\x32\x0e\xac\xc8\xbf\x86\x71\xf9\xbf\x29\x8c\xb1\x4a\x78\xcc\x73\
-\xac\x17\xb2\x8c\xf0\xd8\x05\x2a\xba\xd7\x62\x74\xfb\xd3\xe5\xb9\
-\xe2\xaf\xd9\x93\xd7\xb0\x0d\xcb\xba\x8a\x03\xfc\xd2\x31\xe2\x1b\
-\x07\x2c\x92\xea\xcc\xef\x64\x98\xce\x24\x6f\x1b\x09\xa9\x39\xd6\
-\x77\x2d\x2d\xca\xd9\x8e\x0d\x94\x5f\x7b\x11\xf8\x6d\xb0\xdb\x16\
-\xab\x23\x75\x8d\x0e\x3b\xe2\xa3\xbc\x7f\x3f\x0a\xf4\xe3\x00\x31\
-\xb3\x20\x19\x57\x81\x9c\xd4\x8a\xec\x2d\x72\x74\x8a\x16\x89\x30\
-\x2c\x90\x34\xa6\x62\xa1\x36\xb3\x4c\x6e\xb3\xd3\xb1\x3d\xb4\xf6\
-\x36\x7a\x4f\x93\xe9\x33\x08\xe7\xce\xc0\x8a\xcc\x50\x75\x45\xa5\
-\xb9\x84\x4a\x43\x25\x64\x4b\x1a\x12\x94\x11\x30\xe4\x50\x47\x40\
-\x8b\x3c\x1a\x87\xb9\x68\xd8\x73\x68\x22\x45\x92\x33\x92\x52\x73\
-\x61\x13\x79\x98\xa0\x4f\xb7\x6d\x83\x1f\x84\x91\x2b\x90\xf3\xc4\
-\x64\xbf\x10\x42\x36\xba\x1b\x9e\xab\x75\x18\x05\xde\xd3\xb6\xcb\
-\x76\x86\x1e\x81\x79\x51\xcc\x06\x86\x8d\x2b\xd4\xbe\xba\x9d\xe8\
-\x75\x66\x52\x95\xea\x52\xc2\x63\x9e\x8b\x50\xe4\x1e\xe1\xd7\x79\
-\x7c\x19\x12\x97\x3f\xa6\xe2\xe5\x20\x9f\x0f\x48\x24\x8d\x6d\x24\
-\x3b\x76\xa3\xf3\xc4\xf3\x4a\x87\x09\x5d\x30\x99\x36\xd8\x81\x12\
-\x1f\x19\x46\x85\x8a\xa3\x95\x5d\x47\xd7\x01\x66\xfb\x8f\xd3\x76\
-\xe9\x5c\x86\x9e\x73\xa4\xce\x00\xce\x9c\x44\xec\x17\x38\xa1\xf1\
-\x94\x6e\x04\xab\xd0\xf4\xe7\x1f\x19\xb8\x48\x74\x37\xc0\x25\x14\
-\x64\x41\xef\xc9\xec\x57\x32\x84\x03\xd0\xde\x71\x4e\xf1\xb9\x73\
-\x53\x72\x4d\x0a\x96\x52\x5a\x0e\xac\xe9\x45\xd4\x0a\x84\x53\x90\
-\xcb\x40\xb8\x02\xe1\x84\x0e\x2d\x53\x19\x40\xd2\x9c\xa4\x77\xbc\
-\x8f\x2a\x46\x52\xeb\x73\x23\x62\x5b\x72\xc0\x11\x48\x1d\x0d\x0b\
-\x93\x8b\x79\xe3\x9a\xe3\x70\xac\x32\xdb\x8b\x52\x64\x38\x07\x72\
-\xdf\xfc\xd3\xb0\xbd\x69\x48\x65\x7b\x51\xd3\x94\x46\x6d\x53\x06\
-\xe9\x6c\x1c\x4e\x78\x3a\xee\x7f\xec\x0c\xb8\xea\x32\x0f\xd8\xa0\
-\x4b\x04\x74\x8c\x1d\xc8\x21\x08\xe8\x3e\x29\xed\x90\x72\x2b\xe2\
-\x99\x52\x7c\x9d\xee\x26\x42\x1e\x1e\x5d\x74\x10\x56\x05\x70\xf2\
-\xe0\x54\x5a\x2a\xb0\x64\x05\x95\xf2\x34\x05\x52\x50\x09\xa3\x03\
-\x72\x99\x33\x1e\x02\x2a\x02\x36\x89\x44\x30\xb4\x12\x4a\x4a\x43\
-\xe1\x7a\x68\xf9\x77\x35\x3a\x3a\x9b\x94\x03\x42\x88\x61\x34\x78\
-\xda\x34\x1c\x41\x21\xe9\xe2\x4a\x75\x02\x0d\x3b\x3d\x9c\x6f\xf5\
-\x55\x64\xcc\x2b\xce\xee\x3b\xa7\x8d\xc3\x42\xcf\xc3\x1d\x94\x0a\
-\xd2\x2e\x57\x50\xa7\x78\x04\x96\xaa\x50\x9c\xa3\x0e\x12\xc9\x0c\
-\xfc\xac\xa7\xa2\x11\xc3\x68\xb6\xec\x64\x10\x58\x68\x9e\x7c\x09\
-\xd1\x99\x79\x2c\xac\x5f\x86\x37\xe7\xcf\x45\x24\x54\xa1\x34\xd2\
-\x7e\x0a\x27\x5a\x8f\x62\x57\xf2\x5b\xcc\xee\x4b\x60\xb9\xa8\xa7\
-\xe0\xc4\x20\x04\x6c\xdd\x79\x1c\x3a\xdc\x78\x3c\x06\x3f\x93\x53\
-\x0e\x0c\xa7\xdb\xdb\xc4\xdc\xa6\x96\x23\xc8\xcd\xf2\xb0\x76\xd1\
-\x56\xb4\xf5\xfc\x86\x03\x67\xd7\xa3\x23\xd9\xa7\x34\x6a\x47\x8f\
-\xc2\xcc\x89\xcf\x61\xc3\xd2\x8f\xb0\x6d\xff\x3a\x7c\x97\x38\x8b\
-\x89\xc1\xd4\xab\x64\x01\x57\x37\x8a\x26\xd6\x17\x25\x16\xfb\xd7\
-\x8c\x5f\xca\x72\x73\xe6\x48\x92\xa9\xff\x10\xf7\xfb\xb0\x7a\xe9\
-\x26\x34\x5e\xd8\x84\x8e\x44\x1b\xb2\xd4\x02\x77\x2c\xd1\x7a\x2b\
-\x7f\x10\x38\xda\xf6\x15\x6a\x7a\x1a\xb0\xf2\xa9\x0d\xf8\x60\xdf\
-\x7a\xcc\xad\x4c\x0c\x44\x80\xd9\xc9\xc7\x1d\x8d\x02\x75\x5f\xb7\
-\x2b\xf6\x5e\xb5\xb7\x93\x14\xa8\xd8\xc8\x40\x5f\x16\x42\x84\xd3\
-\xa1\xfa\xd3\x78\x66\xe9\x4b\xf8\xeb\xf2\xe7\xe8\x4e\xb5\xa9\x60\
-\x7c\x31\x90\xac\x3c\x17\xa3\x77\xc7\x49\x67\xf1\xbd\x2f\xa0\xf1\
-\xe2\x37\x83\x1d\xb0\xd0\x9b\x08\xd0\xd9\x03\x4c\xa8\xb5\x75\xae\
-\x8b\xc1\x91\x6b\xe3\xcb\x56\x4d\xd6\x17\x04\xe5\x83\x83\xcf\xbe\
-\xff\x07\x2f\x4e\xa8\x44\xc3\xf9\x53\xc8\xb3\x0e\x11\xdc\x0f\xca\
-\xeb\x78\x6c\x07\xba\x0e\x5c\x8a\x9f\xc2\xbc\xe9\x0b\xd0\x3e\x36\
-\x53\x76\x80\x33\x30\xa0\x55\x39\xea\x18\x75\xb5\x06\xf8\x6b\x21\
-\x40\x3c\x39\x44\x08\x05\x84\x00\x73\x24\x12\x38\xb0\x2a\x81\x8e\
-\xf4\x31\xe5\xcf\x96\x27\x86\xe6\xee\x8e\x67\xcb\x73\xef\x1c\x14\
-\xe8\x4c\x1d\x2b\xa5\xb8\xba\xf4\xb4\xf7\xa4\xbf\xf8\xb9\x31\xf2\
-\x8a\x2c\xde\x72\xc4\xb5\xaf\x57\x45\x24\x74\x59\x96\x08\x51\xc8\
-\xee\xc3\x40\x92\xdb\xb0\x75\x7d\xca\xb2\x4e\xd2\x6f\x2d\x35\x21\
-\x36\x35\x8a\x84\x30\x45\x0d\x5f\x03\x6e\xe2\xca\xe9\x2e\xd8\x8a\
-\xfd\xf3\x1f\xe4\xdb\x34\xdd\x7c\x72\xba\xf3\x65\x88\x84\xdb\x9f\
-\xd4\x9e\xbe\xf5\x13\x95\x69\x47\x57\x51\xee\x90\xcc\x87\xc6\xc3\
-\xd4\x63\xd6\xa0\x96\x11\xe0\xc3\xb8\xa4\x6e\x5b\xfd\xba\xe3\x0d\
-\x3c\x55\x41\x1c\xcd\xb1\x18\x66\xd7\x8c\x37\xfd\x21\x18\x78\xeb\
-\xa9\xa0\xb0\x2a\x5c\xd3\x6d\x29\xd3\x3a\xbb\x54\x57\x6f\x66\x30\
-\x1d\x73\xdf\xec\x33\x72\x33\x4f\xf6\xca\x9f\xd8\x7e\xae\x1a\x5f\
-\xde\x3e\x81\x0c\x93\xa1\x7c\x41\x5d\x82\x4b\x0f\x47\xed\x9a\x3e\
-\xc3\x9d\xf2\xdc\x79\xa0\x9d\xd6\x60\x60\xb2\xdc\xf4\xc3\xc7\x56\
-\x37\x6f\x0d\x3e\xac\x99\x8a\xc5\x0f\x3d\xa0\x71\xe4\x8f\x8f\x9c\
-\xb9\x44\xa9\xab\x99\xa3\xaf\xc8\x47\x8e\x11\x02\x17\xb0\xf7\xd0\
-\x66\xac\xa2\x99\xcb\xf6\x08\x38\xc0\x09\x17\x5c\x38\x8c\xe6\xf1\
-\x77\x61\x4a\x57\x1c\x33\xb8\x91\x55\x8d\x21\xd8\xc3\x74\x63\x0a\
-\x69\xa2\x75\xd2\x17\x51\xf3\x71\xa0\xeb\x22\xf6\x35\x6c\xc1\x7a\
-\x9a\xba\xc2\x55\x7a\x24\x10\x28\x7e\x2e\x54\x91\x4c\xaa\x7f\x1c\
-\x8f\x4c\x99\x8b\x37\x42\x63\x70\x4f\xff\xaf\x19\x3f\xae\xda\xc1\
-\x27\x2d\x07\xf0\x2b\xcd\xb4\x93\xf4\xf2\xf1\x8f\x94\x03\x45\x27\
-\x38\xa3\xaa\x49\x88\x8e\x18\x6d\xe6\x60\x78\x96\x24\xa1\x3a\x8b\
-\x98\xe1\x5b\x01\x37\xfa\x95\x35\xcc\xef\x50\xd7\x7c\xf0\x86\xfa\
-\xed\xcf\x58\xf0\xcd\x3c\x0d\xfd\x89\x50\xaa\x4c\xff\x0b\x30\x00\
-\x55\xce\x74\xfd\x22\x80\x6f\xfb\x00\x00\x00\x00\x49\x45\x4e\x44\
-\xae\x42\x60\x82\
-\x00\x00\x06\xf7\
-\x89\
-\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
-\x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\
-\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\
-\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\
-\x79\x71\xc9\x65\x3c\x00\x00\x06\x99\x49\x44\x41\x54\x78\xda\xb4\
-\x57\x69\x6c\x55\x45\x18\x3d\x33\x77\x7b\x8f\x52\x16\x03\x14\x50\
-\x22\x21\x11\xd4\x18\x13\x85\x44\x8d\x81\xfe\x21\x6e\xe0\x9e\x18\
-\x5c\x82\xd5\x20\x6e\x18\x0c\xc6\x3f\x1a\x89\x71\x41\xa3\x7f\x04\
-\x94\xb8\x21\x06\x88\x46\x0c\x2a\xb2\x48\xd0\x48\x48\x84\x82\x28\
-\x34\xa6\x62\x69\x81\x1a\xca\x22\xa8\x50\x4a\x5f\xe9\xdb\xee\x78\
-\x66\xe6\xbe\xf7\x6e\xdb\xf7\x6a\x13\xf4\x26\x5f\x66\xe6\x2e\x73\
-\xce\x77\xbe\xf9\xbe\x99\x2b\x00\xb8\x77\x2f\xda\xf0\x55\x2e\x14\
-\x33\xf2\x61\x88\xff\xeb\x72\xa4\x84\x2b\xd5\xc6\xb5\xcf\xcd\xbc\
-\x83\xc3\x5c\xe1\xbe\x4b\x4b\x66\x09\xfe\xe9\xf3\xb7\x20\x9d\x07\
-\x94\xe0\x1d\xd1\xeb\x6b\x75\x1e\xc8\xca\x4e\x17\x38\xc0\xbd\xaf\
-\x6e\x9a\xa1\xf1\x68\x67\xe3\x04\x12\xda\xf3\x41\xec\x6c\x3f\xc9\
-\xf7\x75\xc7\x63\xab\x22\xe0\x72\x86\x7e\xda\xde\xc4\xe9\x94\xa0\
-\xbf\x37\x5c\xc4\xae\x55\x38\xd1\x9b\x80\x50\xd1\xc7\x82\xe0\x53\
-\x86\x45\xee\x57\x71\x5c\x95\x80\xa8\x4e\x40\x0e\x4e\xf2\x99\x0f\
-\x91\xe0\xb7\x49\xb6\x7e\x40\x97\x02\x08\x87\x4c\x3d\xb6\x2e\x5b\
-\xa9\xcd\xa7\xd6\x6c\x85\x6b\xfb\x70\xe0\x64\x43\x6c\x7b\xf1\x5d\
-\xcb\xc7\xe2\xf4\xd0\xd7\x8d\x0f\x64\xc2\x10\x86\x1a\x51\x0d\x39\
-\x9c\x0c\x06\x13\xbc\x2a\x89\x70\x50\x00\x49\x60\x24\x09\x16\x68\
-\x02\xec\xd3\x0c\xb0\x13\x19\x09\x08\x0d\xaa\xc1\x45\x44\x42\x50\
-\xf7\xd4\x39\x33\xac\x74\xf5\x20\xe0\x0a\x4b\x4f\x11\x58\x0d\xa2\
-\xf7\x34\x54\xd1\x5b\x12\xd0\x9e\x23\xa1\x3d\xf7\xa1\x34\xb8\xe7\
-\x41\xb9\xd6\x63\x11\x11\x50\x06\xd4\x12\x10\x06\x55\x40\xa5\x1d\
-\x33\xef\x80\x08\x38\xfa\x45\x5f\x2b\x41\xe6\x09\x97\x92\xf3\x31\
-\xfb\x22\x36\x16\x01\xef\xf9\x1c\x7b\xec\x3b\xec\x3b\x7c\x4e\x53\
-\xf4\x56\x14\x09\xb8\x46\x7e\xb3\x00\x39\xa9\x33\x10\x02\x3a\x3c\
-\xae\xb4\x4b\x44\x83\xa8\xa0\x04\x58\x04\xd6\xa6\xfb\x3e\x5b\x97\
-\xad\xab\x63\x6d\x4d\xc8\x08\x54\xcb\xce\x56\x40\x9a\x78\x2a\xc7\
-\xce\x1b\xc6\x73\xaf\x12\x01\x8d\x2f\x98\x2f\x8a\x1e\x1a\xe0\x20\
-\xf2\xbc\x00\x9c\x88\xee\x91\x80\xf0\xac\xf7\x45\xf0\x08\xd8\x4e\
-\xe9\x58\x15\x32\x39\x23\x2b\x4b\x80\x01\x4f\x89\xbe\xa9\x22\xfb\
-\xac\x81\x20\x06\xde\xa3\x75\x4d\x32\x8b\xa0\xa4\x86\x7d\xd7\xb1\
-\x21\x29\x98\x17\xb5\x9c\x59\x08\x41\xa2\x02\x2e\x87\x59\xce\xff\
-\x3e\x02\x34\x2e\xda\x7c\xc2\x06\xba\x17\x81\xb0\xb8\x06\x6c\x8c\
-\x0b\xb1\xd6\x72\x5b\x12\x4e\x5f\x25\xcc\x3b\x3a\x24\xfa\x99\x17\
-\xbd\x1b\xb5\x9e\x25\x23\xa9\x52\xc0\xb0\x1e\x5a\xb8\x05\x43\xae\
-\xb8\x1c\x72\xfc\x04\xfc\xf4\xfc\xe7\xe9\x02\x89\x3e\x0a\xe8\x8f\
-\x84\xef\xf5\x04\xf7\xdd\xa2\x2a\x45\x8f\x13\x9e\x25\x62\xd2\x93\
-\xc5\xcd\xd4\x88\x2a\x66\x4e\x64\xbc\x27\x49\xc6\x67\xb6\xfc\xf0\
-\xc9\xa7\xf8\x7e\xdc\x21\x74\x9f\x3d\x47\xeb\x42\xd7\x88\x71\xf8\
-\xf1\xe9\x15\xe9\xc8\x5d\x14\xab\x43\x61\x0d\xc0\x8b\x49\x59\xf0\
-\xd0\xb7\x2a\x40\x7b\x18\xe8\x3a\xc0\x94\x74\x59\x17\xba\x52\xc8\
-\x9f\x3e\x0c\xd5\x7e\x0a\xaa\xa3\x03\xf9\x3f\xa8\x70\x7b\x0a\xe8\
-\xc8\xc0\xf9\xbb\x03\xcb\x17\x7e\x89\xa9\x57\x4e\xc2\x05\xc3\xaa\
-\xb1\x7c\xe5\x0a\xdc\x24\xa7\xa3\xbb\x3b\x0d\x3f\x9b\x36\xe5\xce\
-\xed\xbd\x22\x15\xbd\xd7\xf2\x19\x8b\xc7\xd4\x64\x03\xf3\x3f\xa9\
-\xd3\x44\x41\x9d\xda\x8f\xf0\xcc\x71\x28\x12\xd0\x01\x56\x69\xde\
-\xeb\x56\x26\xe6\x48\xb0\x72\x76\x01\x8d\xf7\x5c\x86\x21\x41\x15\
-\x86\x0e\x19\x86\x03\xcd\xfb\x51\x73\xc1\x48\x7c\x76\x64\x1d\xee\
-\x3f\x79\x31\x26\x2f\x7b\xec\xda\x1e\x59\x20\x0a\x75\xd9\x2d\x29\
-\xa0\xfb\xc6\x7b\xcf\xb7\x5e\x8b\x2c\xc2\x13\x0d\x50\xd9\x76\x2e\
-\x18\xd7\x7c\xa0\xcb\xb5\xca\x84\xcc\x04\xae\x70\xc5\xbc\xcb\xe9\
-\x95\x9e\x45\xc3\xd4\x0b\x31\xad\x6e\x26\xae\x1b\x3d\x16\x6f\xaf\
-\xde\x06\x37\xdb\x89\xa6\xa6\xdf\x10\x52\xc5\xc9\xcb\xe6\x4e\xe3\
-\x4b\x47\x75\x62\xc8\x78\x1a\x3a\x86\x92\x9e\xd8\x82\x9b\x08\x79\
-\x9e\x21\x13\x9e\x69\x43\x78\x6c\x0f\xeb\x79\x8a\xa0\x94\x7e\x10\
-\x95\x4a\x46\x99\x61\x94\xe2\x8a\x77\xa8\x40\x3e\x8f\x86\xcb\x46\
-\x60\xda\x43\xb7\x01\x23\xeb\xb8\xc3\xbd\x86\x79\xb9\xd9\x68\x69\
-\x3b\xc1\xda\xe5\x62\xe9\xeb\x6f\x6a\xf0\x56\x1a\xb7\x3e\x64\x64\
-\x01\xbc\xb8\x06\x58\x35\xb4\xe9\x4a\x07\x5d\xeb\x43\xed\xf5\x3e\
-\xa8\x14\x63\xab\x43\x60\x6a\x03\x41\x93\xd2\x54\x48\xe1\xeb\x77\
-\xa5\xad\x78\xdc\x6d\xf6\x8c\x1e\x8a\x69\x0f\x13\x7c\xd4\x83\xc0\
-\x8e\x97\x81\xa6\x0d\x9c\xc3\xc1\xd5\x27\x3f\x40\x53\xcd\xed\x88\
-\x3c\x37\xe0\x3d\xb2\xa0\x10\x02\x53\x5c\x8c\x02\x1c\xe5\x33\x08\
-\xff\x68\xe6\x04\x19\x5b\x78\x34\xb1\x80\x60\x34\x24\x4a\x7d\x53\
-\x91\x59\xf6\x76\xfb\x49\xd4\xce\x21\x48\x0d\xc1\xeb\x5f\x02\x9a\
-\xbf\x36\xfb\xf1\x3b\xf5\x63\x71\xff\x7b\x07\x18\xaa\x94\x46\xe8\
-\x2c\x80\xf7\x21\xa0\x43\xa0\x22\xf9\x55\x5e\x7b\x7e\xd0\xbe\xe1\
-\xc8\xa8\xca\x52\x66\xe6\xaa\xf6\x5a\x24\xf5\x1e\x21\x4d\x25\x16\
-\x22\xc4\x8f\x9d\x2e\x6a\x1f\x89\x3c\x37\xe0\xeb\x8d\x32\x4b\xf6\
-\x8c\xc7\x03\x1f\xef\xb7\x61\xce\x65\xfe\xa5\x12\x46\x0a\xe8\x7d\
-\x3b\x3c\xde\x6a\x83\x23\x4d\x49\xb3\x66\x62\x24\x6c\x79\x65\x28\
-\xa4\x26\x43\xf0\x5d\x6d\x21\x6a\xe7\x6a\xcf\xeb\x62\xe0\x2e\x96\
-\x34\x5e\x8a\x87\x76\xfc\x5a\x3c\x00\x28\xf5\x2f\xa5\xd8\xcc\x4f\
-\xc0\xf0\x28\xc1\xa9\x00\xf4\x09\x86\x8b\x0a\x21\x2b\x79\x98\x2f\
-\x99\x0a\xcd\x8a\x17\xb4\x9d\xbf\x64\x50\xfb\x28\x8f\x79\xa3\xeb\
-\x4a\xb2\x93\xe0\x92\x5f\x2e\xc1\xc3\xfb\x5a\x90\x97\x79\xf4\xb3\
-\x19\xf6\xda\x8e\x69\xe9\xb6\x56\x7d\x48\x23\x70\xa0\xe3\x01\x11\
-\x7a\xa5\x5d\x4e\x44\x27\x1f\x97\x67\x05\x11\xa0\xbe\x3e\x8b\xda\
-\xc7\xef\x8c\x81\xaf\x33\x31\x5f\xbc\x6b\x3c\xe6\x1c\xd9\x8b\x5c\
-\x67\x06\xf9\xae\xb0\x54\xf8\x2b\x11\x28\x28\x63\xea\x40\xc3\xb9\
-\x7e\xcf\x98\x85\x6c\xa9\x5f\x30\x09\xb5\xf3\x66\x01\x63\x62\xe0\
-\x2a\x87\xc5\x3b\x46\xe1\xc9\x55\x7b\xcd\xc9\x4a\xda\xe3\xa5\x4d\
-\xef\x18\x4e\xc5\x10\x98\x0d\x55\x45\x6d\x7c\x1c\x99\x1b\xed\x20\
-\xbb\xe6\xd5\xa0\xf6\x89\xbb\xfa\x82\x6f\x1f\x89\xf9\xab\x9a\x7b\
-\x10\x2d\xcc\xd7\xaf\x02\x42\x94\x06\x52\x94\x71\x39\xd6\x6d\xae\
-\xff\x02\x35\x33\x1e\x00\xc6\x2f\x00\xb6\x3d\x03\xb4\x6c\x34\xb2\
-\x2f\xad\x1f\x85\xf9\xab\x9b\x8b\x5e\xc5\xd3\xab\xd0\x17\xa2\x82\
-\x02\xae\x94\x3d\x6e\x54\x32\xfd\xfd\x5f\xcd\x5b\x31\xe9\xc6\x17\
-\x90\x6b\xdb\xca\x32\xd1\xcd\x07\x39\x7c\xf4\xf3\x58\x3c\xb5\xb2\
-\xb9\xe2\x1c\x45\x6f\xa5\x2c\xaf\x80\xe7\xa8\xb3\xb7\xbe\xb2\xa9\
-\x5a\x9f\xdb\xcb\xc5\x89\x05\x16\x79\x2e\xc0\x89\xb9\x5d\x78\x75\
-\x3a\x33\xe0\xd8\x37\x10\xa7\x5b\xb0\xaf\xf5\x2c\x1a\x7f\xbd\x08\
-\x6b\x26\x7e\x8c\xb5\x8b\xbe\x63\xb2\xa4\xcb\x06\xda\x64\x2e\xc1\
-\x7d\xe2\x94\x5b\x53\xc3\x68\x13\xae\xb8\x61\xf6\xf5\xb9\x6c\x5a\
-\x95\x41\x57\xf9\x7c\x36\x97\x6a\xff\x3b\x53\x77\x8d\xff\x61\x30\
-\x7c\x8c\x9c\x77\xf3\x70\xac\xff\xf2\xdb\xf4\x96\x9d\xbf\xbf\xb5\
-\xb3\xea\xee\xc3\x9e\xcc\x0b\xa1\x42\xd1\xdf\x2f\x94\xeb\x05\xa2\
-\x71\xcb\xca\xed\xec\x1e\xa2\xb5\xc7\x09\xe8\x85\x5a\x6d\x7f\x45\
-\x20\x2b\x2d\xfe\x67\xef\x9b\x32\xab\xe9\xdc\xc4\xd7\xc6\x79\x6d\
-\x59\xd1\x71\xf0\xad\x77\x36\x1f\xd3\x75\xf6\x14\xad\x6b\x80\x3f\
-\x6f\xfa\xd0\x95\x8a\xfe\x8a\xb2\xf1\x10\xe8\xc1\x69\xda\x99\x0a\
-\x04\xf4\xe4\x49\x39\xf2\xaa\x67\x6b\xda\x1a\xde\x58\xb6\x66\x37\
-\x77\x17\xfc\x19\x81\x77\x46\xdf\xab\x32\x7f\x94\xe5\x08\x84\xbd\
-\xc9\x8a\x01\xfe\x62\x0e\xa6\x8d\x8a\x32\xb3\x23\xb2\x74\x34\xe1\
-\x79\x5d\x03\x25\xa0\xc3\x14\xd8\x5f\xcd\xff\x06\xb8\x70\xfd\x23\
-\xc0\x00\x10\xd7\x1c\xa6\xef\x42\x19\x7e\x00\x00\x00\x00\x49\x45\
-\x4e\x44\xae\x42\x60\x82\
-"
-
-qt_resource_name = "\
-\x00\x05\
-\x00\x6f\xa6\x53\
-\x00\x69\
-\x00\x63\x00\x6f\x00\x6e\x00\x73\
-\x00\x11\
-\x06\xc6\x1b\xe7\
-\x00\x6e\
-\x00\x65\x00\x77\x00\x5f\x00\x6f\x00\x63\x00\x63\x00\x6c\x00\x75\x00\x73\x00\x69\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\
-\
-\x00\x12\
-\x01\xc1\x07\xe7\
-\x00\x65\
-\x00\x64\x00\x69\x00\x74\x00\x5f\x00\x6f\x00\x63\x00\x63\x00\x6c\x00\x75\x00\x73\x00\x69\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\
-\x00\x67\
-"
-
-qt_resource_struct = "\
-\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
-\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x02\
-\x00\x00\x00\x38\x00\x00\x00\x00\x00\x01\x00\x00\x06\xe9\
-\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
-"
-
-def qInitResources():
- QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
-
-def qCleanupResources():
- QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
-
-qInitResources()
diff --git a/image_occlusion_enhanced/resources.qrc b/image_occlusion_enhanced/resources.qrc
deleted file mode 100644
index fe01eef3..00000000
--- a/image_occlusion_enhanced/resources.qrc
+++ /dev/null
@@ -1,7 +0,0 @@
-
-![]()
-