├── LICENSE ├── README.md ├── contrib ├── srvfb.service └── srvfb.socket ├── go.mod ├── go.sum ├── internal ├── fb │ ├── ctypes.go │ ├── fb.go │ ├── types_amd64.go │ └── types_arm.go └── png │ ├── LICENSE │ ├── paeth.go │ ├── reader.go │ └── writer.go └── srvfb.go /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # srvfb - Stream framebuffer content over HTTP 2 | 3 | This repository contains a small webserver that can serve the contents of a 4 | linux framebuffer device as video over HTTP. The video is encoded as a series 5 | of PNGs, which are served in a `multipart/x-mixed-replace` stream. The primary 6 | use case is to stream a [reMarkable][reMarkable] screen to a computer and share 7 | it from there via video-conferencing or capturing it. For that reason, there is 8 | also a proxy-mode, which streams the frames as raw, uncompressed data from the 9 | remarkable and can then do the png-encoding on a more powerful machine. 10 | Whithout that, the framerate is one or two frames per second, which might not 11 | be acceptable (it might be, though). 12 | 13 | This should be considered a tech demo in the current state. The code is not 14 | particularly clean, it's not in any way secured, probably not very efficient 15 | and it's taylored specifically to the reMarkable (e.g. it can only stream 16 | 16-bit grayscale images). Feel free to use it and report any bugs you find, but 17 | I don't make any promises in regards to support or stability and any issues not 18 | directly related to my usecase will likely be closed. 19 | 20 | You can see a short video demonstrating this [in this tweet][video] 21 | 22 | **Note: This project is not compatible with the reMarkable 2 and 23 | [I don't intend to change that](https://github.com/Merovius/srvfb/issues/22). 24 | In general, you should consider this project in maintenance mode at best - I 25 | don't regularly use it anymore, so I'm not really incentivized to improve it.** 26 | 27 | # Installation and usage 28 | 29 | You need a working [Go installation][go] and [ssh-access to your reMarkable][ssh]. 30 | You can then obtain, install and run the code via 31 | 32 | ``` 33 | go get -d -u github.com/Merovius/srvfb 34 | GOARCH=arm GOARM=7 GOOS=linux go build github.com/Merovius/srvfb 35 | scp srvfb root@10.11.99.1: 36 | ssh root@10.11.99.1 ./srvfb -device /dev/fb0 -listen :1234 37 | ``` 38 | 39 | If you then open `http://10.11.99.1:1234/` in your browser (only Chrome is 40 | tested) you should see the stream from your reMarkable. To use proxy-mode, run 41 | (in a separate terminal) 42 | 43 | ``` 44 | go build github.com/Merovius/srvfb 45 | ./srvfb -listen localhost:1234 -proxy 10.11.99.1:1234 46 | ``` 47 | 48 | and open `http://localhost:1234/` in your browser. 49 | 50 | Once you can see the reMarkable screen in your browser (via proxy or not), 51 | clicking on the image should rotate it by 90°. 52 | 53 | This repository also contains systemd unit files to run `srvfb` automatically 54 | (using socket activation). For security reasons, it only listens on the USB 55 | network, though. To use it, run 56 | 57 | ``` 58 | cd $(go env GOPATH)/src/github.com/Merovius/srvfb 59 | GOARCH=arm GOOS=linux go build github.com/Merovius/srvfb 60 | scp srvfb root@10.11.99.1:/usr/bin 61 | scp contrib/srvfb.service contrib/srvfb.socket root@10.11.99.1:/etc/systemd/system 62 | ssh root@10.11.99.1 systemctl enable --now srvfb.socket 63 | ``` 64 | 65 | # License 66 | 67 | Apart where otherwise noted, this code is published under the Apache License, 68 | Version 2.0: 69 | 70 | ``` 71 | Copyright 2018 Axel Wagner 72 | 73 | Licensed under the Apache License, Version 2.0 (the "License"); 74 | you may not use this file except in compliance with the License. 75 | You may obtain a copy of the License at 76 | 77 | http://www.apache.org/licenses/LICENSE-2.0 78 | 79 | Unless required by applicable law or agreed to in writing, software 80 | distributed under the License is distributed on an "AS IS" BASIS, 81 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 82 | See the License for the specific language governing permissions and 83 | limitations under the License. 84 | ``` 85 | 86 | [reMarkable]: https://remarkable.com/ 87 | [go]: https://golang.org/doc/install 88 | [ssh]: https://remarkablewiki.com/tech/ssh 89 | [video]: https://twitter.com/TheMerovius/status/1066455790117097472 90 | -------------------------------------------------------------------------------- /contrib/srvfb.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Framebuffer Server 3 | 4 | [Service] 5 | ExecStart=/usr/bin/srvfb -device /dev/fb0 -idle 1m 6 | Restart=on-failure 7 | 8 | [Install] 9 | WantedBy=multi-user.target 10 | -------------------------------------------------------------------------------- /contrib/srvfb.socket: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Framebuffer Server Socket 3 | 4 | [Socket] 5 | ListenStream=1234 6 | BindToDevice=usb0 7 | 8 | [Install] 9 | WantedBy=sockets.target 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Merovius/srvfb 2 | 3 | go 1.16 4 | 5 | require golang.org/x/sys v0.12.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 h1:SgQ6LNaYJU0JIuEHv9+s6EbhSCwYeAf5Yvj6lpYlqAE= 2 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 3 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 4 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 5 | -------------------------------------------------------------------------------- /internal/fb/ctypes.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | // generate with: GOARCH=arm go tool cgo -godefs ctypes.go | gofmt > types_arm.go 5 | 6 | // Copyright 2018 Axel Wagner 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | package fb 21 | 22 | /* 23 | #define CONFIG_FB_BACKLIGHT 24 | #include 25 | #include 26 | #include 27 | */ 28 | import "C" 29 | 30 | type FixScreeninfo C.struct_fb_fix_screeninfo 31 | 32 | type Bitfield C.struct_fb_bitfield 33 | 34 | type VarScreeninfo C.struct_fb_var_screeninfo 35 | 36 | type Cmap C.struct_fb_cmap 37 | 38 | type Con2fbmap C.struct_fb_con2fbmap 39 | 40 | type Vblank C.struct_fb_vblank 41 | 42 | type Copyarea C.struct_fb_copyarea 43 | 44 | type Fillrect C.struct_fb_fillrect 45 | 46 | type Image C.struct_fb_image 47 | 48 | type Curpos C.struct_fbcurpos 49 | 50 | type Cursor C.struct_fb_cursor 51 | 52 | const ( 53 | FB_MAX = C.FB_MAX 54 | FBIOGET_VSCREENINFO = C.FBIOGET_VSCREENINFO 55 | FBIOPUT_VSCREENINFO = C.FBIOPUT_VSCREENINFO 56 | FBIOGET_FSCREENINFO = C.FBIOGET_FSCREENINFO 57 | FBIOGETCMAP = C.FBIOGETCMAP 58 | FBIOPUTCMAP = C.FBIOPUTCMAP 59 | FBIOPAN_DISPLAY = C.FBIOPAN_DISPLAY 60 | FBIO_CURSOR = C.FBIO_CURSOR 61 | FBIOGET_CON2FBMAP = C.FBIOGET_CON2FBMAP 62 | FBIOPUT_CON2FBMAP = C.FBIOPUT_CON2FBMAP 63 | FBIOBLANK = C.FBIOBLANK 64 | FBIOGET_VBLANK = C.FBIOGET_VBLANK 65 | FBIO_ALLOC = C.FBIO_ALLOC 66 | FBIO_FREE = C.FBIO_FREE 67 | FBIOGET_GLYPH = C.FBIOGET_GLYPH 68 | FBIOGET_HWCINFO = C.FBIOGET_HWCINFO 69 | FBIOPUT_MODEINFO = C.FBIOPUT_MODEINFO 70 | FBIOGET_DISPINFO = C.FBIOGET_DISPINFO 71 | FBIO_WAITFORVSYNC = C.FBIO_WAITFORVSYNC 72 | FB_TYPE_PACKED_PIXELS = C.FB_TYPE_PACKED_PIXELS 73 | FB_TYPE_PLANES = C.FB_TYPE_PLANES 74 | FB_TYPE_INTERLEAVED_PLANES = C.FB_TYPE_INTERLEAVED_PLANES 75 | FB_TYPE_TEXT = C.FB_TYPE_TEXT 76 | FB_TYPE_VGA_PLANES = C.FB_TYPE_VGA_PLANES 77 | FB_TYPE_FOURCC = C.FB_TYPE_FOURCC 78 | FB_AUX_TEXT_MDA = C.FB_AUX_TEXT_MDA 79 | FB_AUX_TEXT_CGA = C.FB_AUX_TEXT_CGA 80 | FB_AUX_TEXT_S3_MMIO = C.FB_AUX_TEXT_S3_MMIO 81 | FB_AUX_TEXT_MGA_STEP16 = C.FB_AUX_TEXT_MGA_STEP16 82 | FB_AUX_TEXT_MGA_STEP8 = C.FB_AUX_TEXT_MGA_STEP8 83 | FB_AUX_TEXT_SVGA_GROUP = C.FB_AUX_TEXT_SVGA_GROUP 84 | FB_AUX_TEXT_SVGA_MASK = C.FB_AUX_TEXT_SVGA_MASK 85 | FB_AUX_TEXT_SVGA_STEP2 = C.FB_AUX_TEXT_SVGA_STEP2 86 | FB_AUX_TEXT_SVGA_STEP4 = C.FB_AUX_TEXT_SVGA_STEP4 87 | FB_AUX_TEXT_SVGA_STEP8 = C.FB_AUX_TEXT_SVGA_STEP8 88 | FB_AUX_TEXT_SVGA_STEP16 = C.FB_AUX_TEXT_SVGA_STEP16 89 | FB_AUX_TEXT_SVGA_LAST = C.FB_AUX_TEXT_SVGA_LAST 90 | FB_AUX_VGA_PLANES_VGA4 = C.FB_AUX_VGA_PLANES_VGA4 91 | FB_AUX_VGA_PLANES_CFB4 = C.FB_AUX_VGA_PLANES_CFB4 92 | FB_AUX_VGA_PLANES_CFB8 = C.FB_AUX_VGA_PLANES_CFB8 93 | FB_VISUAL_MONO01 = C.FB_VISUAL_MONO01 94 | FB_VISUAL_MONO10 = C.FB_VISUAL_MONO10 95 | FB_VISUAL_TRUECOLOR = C.FB_VISUAL_TRUECOLOR 96 | FB_VISUAL_PSEUDOCOLOR = C.FB_VISUAL_PSEUDOCOLOR 97 | FB_VISUAL_DIRECTCOLOR = C.FB_VISUAL_DIRECTCOLOR 98 | FB_VISUAL_STATIC_PSEUDOCOLOR = C.FB_VISUAL_STATIC_PSEUDOCOLOR 99 | FB_VISUAL_FOURCC = C.FB_VISUAL_FOURCC 100 | FB_ACCEL_NONE = C.FB_ACCEL_NONE 101 | FB_ACCEL_ATARIBLITT = C.FB_ACCEL_ATARIBLITT 102 | FB_ACCEL_AMIGABLITT = C.FB_ACCEL_AMIGABLITT 103 | FB_ACCEL_S3_TRIO64 = C.FB_ACCEL_S3_TRIO64 104 | FB_ACCEL_NCR_77C32BLT = C.FB_ACCEL_NCR_77C32BLT 105 | FB_ACCEL_S3_VIRGE = C.FB_ACCEL_S3_VIRGE 106 | FB_ACCEL_ATI_MACH64GX = C.FB_ACCEL_ATI_MACH64GX 107 | FB_ACCEL_DEC_TGA = C.FB_ACCEL_DEC_TGA 108 | FB_ACCEL_ATI_MACH64CT = C.FB_ACCEL_ATI_MACH64CT 109 | FB_ACCEL_ATI_MACH64VT = C.FB_ACCEL_ATI_MACH64VT 110 | FB_ACCEL_ATI_MACH64GT = C.FB_ACCEL_ATI_MACH64GT 111 | FB_ACCEL_SUN_CREATOR = C.FB_ACCEL_SUN_CREATOR 112 | FB_ACCEL_SUN_CGSIX = C.FB_ACCEL_SUN_CGSIX 113 | FB_ACCEL_SUN_LEO = C.FB_ACCEL_SUN_LEO 114 | FB_ACCEL_IMS_TWINTURBO = C.FB_ACCEL_IMS_TWINTURBO 115 | FB_ACCEL_3DLABS_PERMEDIA2 = C.FB_ACCEL_3DLABS_PERMEDIA2 116 | FB_ACCEL_MATROX_MGA2064W = C.FB_ACCEL_MATROX_MGA2064W 117 | FB_ACCEL_MATROX_MGA1064SG = C.FB_ACCEL_MATROX_MGA1064SG 118 | FB_ACCEL_MATROX_MGA2164W = C.FB_ACCEL_MATROX_MGA2164W 119 | FB_ACCEL_MATROX_MGA2164W_AGP = C.FB_ACCEL_MATROX_MGA2164W_AGP 120 | FB_ACCEL_MATROX_MGAG100 = C.FB_ACCEL_MATROX_MGAG100 121 | FB_ACCEL_MATROX_MGAG200 = C.FB_ACCEL_MATROX_MGAG200 122 | FB_ACCEL_SUN_CG14 = C.FB_ACCEL_SUN_CG14 123 | FB_ACCEL_SUN_BWTWO = C.FB_ACCEL_SUN_BWTWO 124 | FB_ACCEL_SUN_CGTHREE = C.FB_ACCEL_SUN_CGTHREE 125 | FB_ACCEL_SUN_TCX = C.FB_ACCEL_SUN_TCX 126 | FB_ACCEL_MATROX_MGAG400 = C.FB_ACCEL_MATROX_MGAG400 127 | FB_ACCEL_NV3 = C.FB_ACCEL_NV3 128 | FB_ACCEL_NV4 = C.FB_ACCEL_NV4 129 | FB_ACCEL_NV5 = C.FB_ACCEL_NV5 130 | FB_ACCEL_CT_6555x = C.FB_ACCEL_CT_6555x 131 | FB_ACCEL_3DFX_BANSHEE = C.FB_ACCEL_3DFX_BANSHEE 132 | FB_ACCEL_ATI_RAGE128 = C.FB_ACCEL_ATI_RAGE128 133 | FB_ACCEL_IGS_CYBER2000 = C.FB_ACCEL_IGS_CYBER2000 134 | FB_ACCEL_IGS_CYBER2010 = C.FB_ACCEL_IGS_CYBER2010 135 | FB_ACCEL_IGS_CYBER5000 = C.FB_ACCEL_IGS_CYBER5000 136 | FB_ACCEL_SIS_GLAMOUR = C.FB_ACCEL_SIS_GLAMOUR 137 | FB_ACCEL_3DLABS_PERMEDIA3 = C.FB_ACCEL_3DLABS_PERMEDIA3 138 | FB_ACCEL_ATI_RADEON = C.FB_ACCEL_ATI_RADEON 139 | FB_ACCEL_I810 = C.FB_ACCEL_I810 140 | FB_ACCEL_SIS_GLAMOUR_2 = C.FB_ACCEL_SIS_GLAMOUR_2 141 | FB_ACCEL_SIS_XABRE = C.FB_ACCEL_SIS_XABRE 142 | FB_ACCEL_I830 = C.FB_ACCEL_I830 143 | FB_ACCEL_NV_10 = C.FB_ACCEL_NV_10 144 | FB_ACCEL_NV_20 = C.FB_ACCEL_NV_20 145 | FB_ACCEL_NV_30 = C.FB_ACCEL_NV_30 146 | FB_ACCEL_NV_40 = C.FB_ACCEL_NV_40 147 | FB_ACCEL_XGI_VOLARI_V = C.FB_ACCEL_XGI_VOLARI_V 148 | FB_ACCEL_XGI_VOLARI_Z = C.FB_ACCEL_XGI_VOLARI_Z 149 | FB_ACCEL_OMAP1610 = C.FB_ACCEL_OMAP1610 150 | FB_ACCEL_TRIDENT_TGUI = C.FB_ACCEL_TRIDENT_TGUI 151 | FB_ACCEL_TRIDENT_3DIMAGE = C.FB_ACCEL_TRIDENT_3DIMAGE 152 | FB_ACCEL_TRIDENT_BLADE3D = C.FB_ACCEL_TRIDENT_BLADE3D 153 | FB_ACCEL_TRIDENT_BLADEXP = C.FB_ACCEL_TRIDENT_BLADEXP 154 | FB_ACCEL_CIRRUS_ALPINE = C.FB_ACCEL_CIRRUS_ALPINE 155 | FB_ACCEL_NEOMAGIC_NM2070 = C.FB_ACCEL_NEOMAGIC_NM2070 156 | FB_ACCEL_NEOMAGIC_NM2090 = C.FB_ACCEL_NEOMAGIC_NM2090 157 | FB_ACCEL_NEOMAGIC_NM2093 = C.FB_ACCEL_NEOMAGIC_NM2093 158 | FB_ACCEL_NEOMAGIC_NM2097 = C.FB_ACCEL_NEOMAGIC_NM2097 159 | FB_ACCEL_NEOMAGIC_NM2160 = C.FB_ACCEL_NEOMAGIC_NM2160 160 | FB_ACCEL_NEOMAGIC_NM2200 = C.FB_ACCEL_NEOMAGIC_NM2200 161 | FB_ACCEL_NEOMAGIC_NM2230 = C.FB_ACCEL_NEOMAGIC_NM2230 162 | FB_ACCEL_NEOMAGIC_NM2360 = C.FB_ACCEL_NEOMAGIC_NM2360 163 | FB_ACCEL_NEOMAGIC_NM2380 = C.FB_ACCEL_NEOMAGIC_NM2380 164 | FB_ACCEL_PXA3XX = C.FB_ACCEL_PXA3XX 165 | FB_ACCEL_SAVAGE4 = C.FB_ACCEL_SAVAGE4 166 | FB_ACCEL_SAVAGE3D = C.FB_ACCEL_SAVAGE3D 167 | FB_ACCEL_SAVAGE3D_MV = C.FB_ACCEL_SAVAGE3D_MV 168 | FB_ACCEL_SAVAGE2000 = C.FB_ACCEL_SAVAGE2000 169 | FB_ACCEL_SAVAGE_MX_MV = C.FB_ACCEL_SAVAGE_MX_MV 170 | FB_ACCEL_SAVAGE_MX = C.FB_ACCEL_SAVAGE_MX 171 | FB_ACCEL_SAVAGE_IX_MV = C.FB_ACCEL_SAVAGE_IX_MV 172 | FB_ACCEL_SAVAGE_IX = C.FB_ACCEL_SAVAGE_IX 173 | FB_ACCEL_PROSAVAGE_PM = C.FB_ACCEL_PROSAVAGE_PM 174 | FB_ACCEL_PROSAVAGE_KM = C.FB_ACCEL_PROSAVAGE_KM 175 | FB_ACCEL_S3TWISTER_P = C.FB_ACCEL_S3TWISTER_P 176 | FB_ACCEL_S3TWISTER_K = C.FB_ACCEL_S3TWISTER_K 177 | FB_ACCEL_SUPERSAVAGE = C.FB_ACCEL_SUPERSAVAGE 178 | FB_ACCEL_PROSAVAGE_DDR = C.FB_ACCEL_PROSAVAGE_DDR 179 | FB_ACCEL_PROSAVAGE_DDRK = C.FB_ACCEL_PROSAVAGE_DDRK 180 | FB_ACCEL_PUV3_UNIGFX = C.FB_ACCEL_PUV3_UNIGFX 181 | FB_CAP_FOURCC = C.FB_CAP_FOURCC 182 | FB_NONSTD_HAM = C.FB_NONSTD_HAM 183 | FB_NONSTD_REV_PIX_IN_B = C.FB_NONSTD_REV_PIX_IN_B 184 | FB_ACTIVATE_NOW = C.FB_ACTIVATE_NOW 185 | FB_ACTIVATE_NXTOPEN = C.FB_ACTIVATE_NXTOPEN 186 | FB_ACTIVATE_TEST = C.FB_ACTIVATE_TEST 187 | FB_ACTIVATE_MASK = C.FB_ACTIVATE_MASK 188 | FB_ACTIVATE_VBL = C.FB_ACTIVATE_VBL 189 | FB_CHANGE_CMAP_VBL = C.FB_CHANGE_CMAP_VBL 190 | FB_ACTIVATE_ALL = C.FB_ACTIVATE_ALL 191 | FB_ACTIVATE_FORCE = C.FB_ACTIVATE_FORCE 192 | FB_ACTIVATE_INV_MODE = C.FB_ACTIVATE_INV_MODE 193 | FB_ACCELF_TEXT = C.FB_ACCELF_TEXT 194 | FB_SYNC_HOR_HIGH_ACT = C.FB_SYNC_HOR_HIGH_ACT 195 | FB_SYNC_VERT_HIGH_ACT = C.FB_SYNC_VERT_HIGH_ACT 196 | FB_SYNC_EXT = C.FB_SYNC_EXT 197 | FB_SYNC_COMP_HIGH_ACT = C.FB_SYNC_COMP_HIGH_ACT 198 | FB_SYNC_BROADCAST = C.FB_SYNC_BROADCAST 199 | FB_SYNC_ON_GREEN = C.FB_SYNC_ON_GREEN 200 | FB_VMODE_NONINTERLACED = C.FB_VMODE_NONINTERLACED 201 | FB_VMODE_INTERLACED = C.FB_VMODE_INTERLACED 202 | FB_VMODE_DOUBLE = C.FB_VMODE_DOUBLE 203 | FB_VMODE_ODD_FLD_FIRST = C.FB_VMODE_ODD_FLD_FIRST 204 | FB_VMODE_MASK = C.FB_VMODE_MASK 205 | FB_VMODE_YWRAP = C.FB_VMODE_YWRAP 206 | FB_VMODE_SMOOTH_XPAN = C.FB_VMODE_SMOOTH_XPAN 207 | FB_VMODE_CONUPDATE = C.FB_VMODE_CONUPDATE 208 | FB_ROTATE_UR = C.FB_ROTATE_UR 209 | FB_ROTATE_CW = C.FB_ROTATE_CW 210 | FB_ROTATE_UD = C.FB_ROTATE_UD 211 | FB_ROTATE_CCW = C.FB_ROTATE_CCW 212 | VESA_NO_BLANKING = C.VESA_NO_BLANKING 213 | VESA_VSYNC_SUSPEND = C.VESA_VSYNC_SUSPEND 214 | VESA_HSYNC_SUSPEND = C.VESA_HSYNC_SUSPEND 215 | VESA_POWERDOWN = C.VESA_POWERDOWN 216 | FB_VBLANK_VBLANKING = C.FB_VBLANK_VBLANKING 217 | FB_VBLANK_HBLANKING = C.FB_VBLANK_HBLANKING 218 | FB_VBLANK_HAVE_VBLANK = C.FB_VBLANK_HAVE_VBLANK 219 | FB_VBLANK_HAVE_HBLANK = C.FB_VBLANK_HAVE_HBLANK 220 | FB_VBLANK_HAVE_COUNT = C.FB_VBLANK_HAVE_COUNT 221 | FB_VBLANK_HAVE_VCOUNT = C.FB_VBLANK_HAVE_VCOUNT 222 | FB_VBLANK_HAVE_HCOUNT = C.FB_VBLANK_HAVE_HCOUNT 223 | FB_VBLANK_VSYNCING = C.FB_VBLANK_VSYNCING 224 | FB_VBLANK_HAVE_VSYNC = C.FB_VBLANK_HAVE_VSYNC 225 | ROP_COPY = C.ROP_COPY 226 | ROP_XOR = C.ROP_XOR 227 | FB_CUR_SETIMAGE = C.FB_CUR_SETIMAGE 228 | FB_CUR_SETPOS = C.FB_CUR_SETPOS 229 | FB_CUR_SETHOT = C.FB_CUR_SETHOT 230 | FB_CUR_SETCMAP = C.FB_CUR_SETCMAP 231 | FB_CUR_SETSHAPE = C.FB_CUR_SETSHAPE 232 | FB_CUR_SETSIZE = C.FB_CUR_SETSIZE 233 | FB_CUR_SETALL = C.FB_CUR_SETALL 234 | FB_BACKLIGHT_LEVELS = C.FB_BACKLIGHT_LEVELS 235 | FB_BACKLIGHT_MAX = C.FB_BACKLIGHT_MAX 236 | ) 237 | -------------------------------------------------------------------------------- /internal/fb/fb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Axel Wagner 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package fb implements framebuffer interaction via ioctls and mmap. 16 | package fb 17 | 18 | import ( 19 | "errors" 20 | "fmt" 21 | "image" 22 | "unsafe" 23 | 24 | "golang.org/x/sys/unix" 25 | ) 26 | 27 | type Device struct { 28 | fd uintptr 29 | mmap []byte 30 | finfo FixScreeninfo 31 | } 32 | 33 | func Open(dev string) (*Device, error) { 34 | fd, err := unix.Open(dev, unix.O_RDWR|unix.O_CLOEXEC, 0) 35 | if err != nil { 36 | return nil, fmt.Errorf("open %s: %v", dev, err) 37 | } 38 | if int(uintptr(fd)) != fd { 39 | unix.Close(fd) 40 | return nil, errors.New("fd overflows") 41 | } 42 | d := &Device{fd: uintptr(fd)} 43 | 44 | _, _, eno := unix.Syscall(unix.SYS_IOCTL, d.fd, FBIOGET_FSCREENINFO, uintptr(unsafe.Pointer(&d.finfo))) 45 | if eno != 0 { 46 | unix.Close(fd) 47 | return nil, fmt.Errorf("FBIOGET_FSCREENINFO: %v", eno) 48 | } 49 | 50 | d.mmap, err = unix.Mmap(fd, 0, int(d.finfo.Smem_len), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED) 51 | if err != nil { 52 | unix.Close(fd) 53 | return nil, fmt.Errorf("mmap: %v", err) 54 | } 55 | return d, nil 56 | } 57 | 58 | func (d *Device) VarScreeninfo() (VarScreeninfo, error) { 59 | var vinfo VarScreeninfo 60 | _, _, eno := unix.Syscall(unix.SYS_IOCTL, d.fd, FBIOGET_VSCREENINFO, uintptr(unsafe.Pointer(&vinfo))) 61 | if eno != 0 { 62 | return vinfo, fmt.Errorf("FBIOGET_VSCREENINFO: %v", eno) 63 | } 64 | return vinfo, nil 65 | } 66 | 67 | func (d *Device) Image() (image.Image, error) { 68 | vinfo, err := d.VarScreeninfo() 69 | if err != nil { 70 | return nil, err 71 | } 72 | if vinfo.Bits_per_pixel != 16 { 73 | return nil, fmt.Errorf("%d bits per pixel unsupported", vinfo.Bits_per_pixel) 74 | } 75 | virtual := image.Rect(0, 0, int(vinfo.Xres_virtual), int(vinfo.Yres_virtual)) 76 | if virtual.Dx()*virtual.Dy()*2 != len(d.mmap) { 77 | return nil, errors.New("virtual resolution doesn't match framebuffer size") 78 | } 79 | visual := image.Rect(int(vinfo.Xoffset), int(vinfo.Yoffset), int(vinfo.Xres), int(vinfo.Yres)) 80 | if !visual.In(virtual) { 81 | return nil, errors.New("visual resolution not contained in virtual resolution") 82 | } 83 | return &image.Gray16{ 84 | Pix: d.mmap, 85 | Stride: int(d.finfo.Line_length), 86 | Rect: visual, 87 | }, nil 88 | } 89 | 90 | func (d *Device) Close() error { 91 | e1 := unix.Munmap(d.mmap) 92 | if e2 := unix.Close(int(d.fd)); e2 != nil { 93 | return e2 94 | } 95 | return e1 96 | } 97 | -------------------------------------------------------------------------------- /internal/fb/types_amd64.go: -------------------------------------------------------------------------------- 1 | // Code generated by cmd/cgo -godefs; DO NOT EDIT. 2 | // cgo -godefs ctypes.go 3 | 4 | package fb 5 | 6 | type FixScreeninfo struct { 7 | Id [16]int8 8 | Smem_start uint64 9 | Smem_len uint32 10 | Type uint32 11 | Type_aux uint32 12 | Visual uint32 13 | Xpanstep uint16 14 | Ypanstep uint16 15 | Ywrapstep uint16 16 | Pad_cgo_0 [2]byte 17 | Line_length uint32 18 | Pad_cgo_1 [4]byte 19 | Mmio_start uint64 20 | Mmio_len uint32 21 | Accel uint32 22 | Capabilities uint16 23 | Reserved [2]uint16 24 | Pad_cgo_2 [2]byte 25 | } 26 | 27 | type Bitfield struct { 28 | Offset uint32 29 | Length uint32 30 | Right uint32 31 | } 32 | 33 | type VarScreeninfo struct { 34 | Xres uint32 35 | Yres uint32 36 | Xres_virtual uint32 37 | Yres_virtual uint32 38 | Xoffset uint32 39 | Yoffset uint32 40 | Bits_per_pixel uint32 41 | Grayscale uint32 42 | Red Bitfield 43 | Green Bitfield 44 | Blue Bitfield 45 | Transp Bitfield 46 | Nonstd uint32 47 | Activate uint32 48 | Height uint32 49 | Width uint32 50 | Accel_flags uint32 51 | Pixclock uint32 52 | Left_margin uint32 53 | Right_margin uint32 54 | Upper_margin uint32 55 | Lower_margin uint32 56 | Hsync_len uint32 57 | Vsync_len uint32 58 | Sync uint32 59 | Vmode uint32 60 | Rotate uint32 61 | Colorspace uint32 62 | Reserved [4]uint32 63 | } 64 | 65 | type Cmap struct { 66 | Start uint32 67 | Len uint32 68 | Red *uint16 69 | Green *uint16 70 | Blue *uint16 71 | Transp *uint16 72 | } 73 | 74 | type Con2fbmap struct { 75 | Console uint32 76 | Framebuffer uint32 77 | } 78 | 79 | type Vblank struct { 80 | Flags uint32 81 | Count uint32 82 | Vcount uint32 83 | Hcount uint32 84 | Reserved [4]uint32 85 | } 86 | 87 | type Copyarea struct { 88 | Dx uint32 89 | Dy uint32 90 | Width uint32 91 | Height uint32 92 | Sx uint32 93 | Sy uint32 94 | } 95 | 96 | type Fillrect struct { 97 | Dx uint32 98 | Dy uint32 99 | Width uint32 100 | Height uint32 101 | Color uint32 102 | Rop uint32 103 | } 104 | 105 | type Image struct { 106 | Dx uint32 107 | Dy uint32 108 | Width uint32 109 | Height uint32 110 | Fg_color uint32 111 | Bg_color uint32 112 | Depth uint8 113 | Pad_cgo_0 [7]byte 114 | Data *int8 115 | Cmap Cmap 116 | } 117 | 118 | type Curpos struct { 119 | X uint16 120 | Y uint16 121 | } 122 | 123 | type Cursor struct { 124 | Set uint16 125 | Enable uint16 126 | Rop uint16 127 | Pad_cgo_0 [2]byte 128 | Mask *int8 129 | Hot Curpos 130 | Pad_cgo_1 [4]byte 131 | Image Image 132 | } 133 | 134 | const ( 135 | FB_MAX = 0x20 136 | FBIOGET_VSCREENINFO = 0x4600 137 | FBIOPUT_VSCREENINFO = 0x4601 138 | FBIOGET_FSCREENINFO = 0x4602 139 | FBIOGETCMAP = 0x4604 140 | FBIOPUTCMAP = 0x4605 141 | FBIOPAN_DISPLAY = 0x4606 142 | FBIO_CURSOR = 0xc0684608 143 | FBIOGET_CON2FBMAP = 0x460f 144 | FBIOPUT_CON2FBMAP = 0x4610 145 | FBIOBLANK = 0x4611 146 | FBIOGET_VBLANK = 0x80204612 147 | FBIO_ALLOC = 0x4613 148 | FBIO_FREE = 0x4614 149 | FBIOGET_GLYPH = 0x4615 150 | FBIOGET_HWCINFO = 0x4616 151 | FBIOPUT_MODEINFO = 0x4617 152 | FBIOGET_DISPINFO = 0x4618 153 | FBIO_WAITFORVSYNC = 0x40044620 154 | FB_TYPE_PACKED_PIXELS = 0x0 155 | FB_TYPE_PLANES = 0x1 156 | FB_TYPE_INTERLEAVED_PLANES = 0x2 157 | FB_TYPE_TEXT = 0x3 158 | FB_TYPE_VGA_PLANES = 0x4 159 | FB_TYPE_FOURCC = 0x5 160 | FB_AUX_TEXT_MDA = 0x0 161 | FB_AUX_TEXT_CGA = 0x1 162 | FB_AUX_TEXT_S3_MMIO = 0x2 163 | FB_AUX_TEXT_MGA_STEP16 = 0x3 164 | FB_AUX_TEXT_MGA_STEP8 = 0x4 165 | FB_AUX_TEXT_SVGA_GROUP = 0x8 166 | FB_AUX_TEXT_SVGA_MASK = 0x7 167 | FB_AUX_TEXT_SVGA_STEP2 = 0x8 168 | FB_AUX_TEXT_SVGA_STEP4 = 0x9 169 | FB_AUX_TEXT_SVGA_STEP8 = 0xa 170 | FB_AUX_TEXT_SVGA_STEP16 = 0xb 171 | FB_AUX_TEXT_SVGA_LAST = 0xf 172 | FB_AUX_VGA_PLANES_VGA4 = 0x0 173 | FB_AUX_VGA_PLANES_CFB4 = 0x1 174 | FB_AUX_VGA_PLANES_CFB8 = 0x2 175 | FB_VISUAL_MONO01 = 0x0 176 | FB_VISUAL_MONO10 = 0x1 177 | FB_VISUAL_TRUECOLOR = 0x2 178 | FB_VISUAL_PSEUDOCOLOR = 0x3 179 | FB_VISUAL_DIRECTCOLOR = 0x4 180 | FB_VISUAL_STATIC_PSEUDOCOLOR = 0x5 181 | FB_VISUAL_FOURCC = 0x6 182 | FB_ACCEL_NONE = 0x0 183 | FB_ACCEL_ATARIBLITT = 0x1 184 | FB_ACCEL_AMIGABLITT = 0x2 185 | FB_ACCEL_S3_TRIO64 = 0x3 186 | FB_ACCEL_NCR_77C32BLT = 0x4 187 | FB_ACCEL_S3_VIRGE = 0x5 188 | FB_ACCEL_ATI_MACH64GX = 0x6 189 | FB_ACCEL_DEC_TGA = 0x7 190 | FB_ACCEL_ATI_MACH64CT = 0x8 191 | FB_ACCEL_ATI_MACH64VT = 0x9 192 | FB_ACCEL_ATI_MACH64GT = 0xa 193 | FB_ACCEL_SUN_CREATOR = 0xb 194 | FB_ACCEL_SUN_CGSIX = 0xc 195 | FB_ACCEL_SUN_LEO = 0xd 196 | FB_ACCEL_IMS_TWINTURBO = 0xe 197 | FB_ACCEL_3DLABS_PERMEDIA2 = 0xf 198 | FB_ACCEL_MATROX_MGA2064W = 0x10 199 | FB_ACCEL_MATROX_MGA1064SG = 0x11 200 | FB_ACCEL_MATROX_MGA2164W = 0x12 201 | FB_ACCEL_MATROX_MGA2164W_AGP = 0x13 202 | FB_ACCEL_MATROX_MGAG100 = 0x14 203 | FB_ACCEL_MATROX_MGAG200 = 0x15 204 | FB_ACCEL_SUN_CG14 = 0x16 205 | FB_ACCEL_SUN_BWTWO = 0x17 206 | FB_ACCEL_SUN_CGTHREE = 0x18 207 | FB_ACCEL_SUN_TCX = 0x19 208 | FB_ACCEL_MATROX_MGAG400 = 0x1a 209 | FB_ACCEL_NV3 = 0x1b 210 | FB_ACCEL_NV4 = 0x1c 211 | FB_ACCEL_NV5 = 0x1d 212 | FB_ACCEL_CT_6555x = 0x1e 213 | FB_ACCEL_3DFX_BANSHEE = 0x1f 214 | FB_ACCEL_ATI_RAGE128 = 0x20 215 | FB_ACCEL_IGS_CYBER2000 = 0x21 216 | FB_ACCEL_IGS_CYBER2010 = 0x22 217 | FB_ACCEL_IGS_CYBER5000 = 0x23 218 | FB_ACCEL_SIS_GLAMOUR = 0x24 219 | FB_ACCEL_3DLABS_PERMEDIA3 = 0x25 220 | FB_ACCEL_ATI_RADEON = 0x26 221 | FB_ACCEL_I810 = 0x27 222 | FB_ACCEL_SIS_GLAMOUR_2 = 0x28 223 | FB_ACCEL_SIS_XABRE = 0x29 224 | FB_ACCEL_I830 = 0x2a 225 | FB_ACCEL_NV_10 = 0x2b 226 | FB_ACCEL_NV_20 = 0x2c 227 | FB_ACCEL_NV_30 = 0x2d 228 | FB_ACCEL_NV_40 = 0x2e 229 | FB_ACCEL_XGI_VOLARI_V = 0x2f 230 | FB_ACCEL_XGI_VOLARI_Z = 0x30 231 | FB_ACCEL_OMAP1610 = 0x31 232 | FB_ACCEL_TRIDENT_TGUI = 0x32 233 | FB_ACCEL_TRIDENT_3DIMAGE = 0x33 234 | FB_ACCEL_TRIDENT_BLADE3D = 0x34 235 | FB_ACCEL_TRIDENT_BLADEXP = 0x35 236 | FB_ACCEL_CIRRUS_ALPINE = 0x35 237 | FB_ACCEL_NEOMAGIC_NM2070 = 0x5a 238 | FB_ACCEL_NEOMAGIC_NM2090 = 0x5b 239 | FB_ACCEL_NEOMAGIC_NM2093 = 0x5c 240 | FB_ACCEL_NEOMAGIC_NM2097 = 0x5d 241 | FB_ACCEL_NEOMAGIC_NM2160 = 0x5e 242 | FB_ACCEL_NEOMAGIC_NM2200 = 0x5f 243 | FB_ACCEL_NEOMAGIC_NM2230 = 0x60 244 | FB_ACCEL_NEOMAGIC_NM2360 = 0x61 245 | FB_ACCEL_NEOMAGIC_NM2380 = 0x62 246 | FB_ACCEL_PXA3XX = 0x63 247 | FB_ACCEL_SAVAGE4 = 0x80 248 | FB_ACCEL_SAVAGE3D = 0x81 249 | FB_ACCEL_SAVAGE3D_MV = 0x82 250 | FB_ACCEL_SAVAGE2000 = 0x83 251 | FB_ACCEL_SAVAGE_MX_MV = 0x84 252 | FB_ACCEL_SAVAGE_MX = 0x85 253 | FB_ACCEL_SAVAGE_IX_MV = 0x86 254 | FB_ACCEL_SAVAGE_IX = 0x87 255 | FB_ACCEL_PROSAVAGE_PM = 0x88 256 | FB_ACCEL_PROSAVAGE_KM = 0x89 257 | FB_ACCEL_S3TWISTER_P = 0x8a 258 | FB_ACCEL_S3TWISTER_K = 0x8b 259 | FB_ACCEL_SUPERSAVAGE = 0x8c 260 | FB_ACCEL_PROSAVAGE_DDR = 0x8d 261 | FB_ACCEL_PROSAVAGE_DDRK = 0x8e 262 | FB_ACCEL_PUV3_UNIGFX = 0xa0 263 | FB_CAP_FOURCC = 0x1 264 | FB_NONSTD_HAM = 0x1 265 | FB_NONSTD_REV_PIX_IN_B = 0x2 266 | FB_ACTIVATE_NOW = 0x0 267 | FB_ACTIVATE_NXTOPEN = 0x1 268 | FB_ACTIVATE_TEST = 0x2 269 | FB_ACTIVATE_MASK = 0xf 270 | FB_ACTIVATE_VBL = 0x10 271 | FB_CHANGE_CMAP_VBL = 0x20 272 | FB_ACTIVATE_ALL = 0x40 273 | FB_ACTIVATE_FORCE = 0x80 274 | FB_ACTIVATE_INV_MODE = 0x100 275 | FB_ACCELF_TEXT = 0x1 276 | FB_SYNC_HOR_HIGH_ACT = 0x1 277 | FB_SYNC_VERT_HIGH_ACT = 0x2 278 | FB_SYNC_EXT = 0x4 279 | FB_SYNC_COMP_HIGH_ACT = 0x8 280 | FB_SYNC_BROADCAST = 0x10 281 | FB_SYNC_ON_GREEN = 0x20 282 | FB_VMODE_NONINTERLACED = 0x0 283 | FB_VMODE_INTERLACED = 0x1 284 | FB_VMODE_DOUBLE = 0x2 285 | FB_VMODE_ODD_FLD_FIRST = 0x4 286 | FB_VMODE_MASK = 0xff 287 | FB_VMODE_YWRAP = 0x100 288 | FB_VMODE_SMOOTH_XPAN = 0x200 289 | FB_VMODE_CONUPDATE = 0x200 290 | FB_ROTATE_UR = 0x0 291 | FB_ROTATE_CW = 0x1 292 | FB_ROTATE_UD = 0x2 293 | FB_ROTATE_CCW = 0x3 294 | VESA_NO_BLANKING = 0x0 295 | VESA_VSYNC_SUSPEND = 0x1 296 | VESA_HSYNC_SUSPEND = 0x2 297 | VESA_POWERDOWN = 0x3 298 | FB_VBLANK_VBLANKING = 0x1 299 | FB_VBLANK_HBLANKING = 0x2 300 | FB_VBLANK_HAVE_VBLANK = 0x4 301 | FB_VBLANK_HAVE_HBLANK = 0x8 302 | FB_VBLANK_HAVE_COUNT = 0x10 303 | FB_VBLANK_HAVE_VCOUNT = 0x20 304 | FB_VBLANK_HAVE_HCOUNT = 0x40 305 | FB_VBLANK_VSYNCING = 0x80 306 | FB_VBLANK_HAVE_VSYNC = 0x100 307 | ROP_COPY = 0x0 308 | ROP_XOR = 0x1 309 | FB_CUR_SETIMAGE = 0x1 310 | FB_CUR_SETPOS = 0x2 311 | FB_CUR_SETHOT = 0x4 312 | FB_CUR_SETCMAP = 0x8 313 | FB_CUR_SETSHAPE = 0x10 314 | FB_CUR_SETSIZE = 0x20 315 | FB_CUR_SETALL = 0xff 316 | FB_BACKLIGHT_LEVELS = 0x80 317 | FB_BACKLIGHT_MAX = 0xff 318 | ) 319 | -------------------------------------------------------------------------------- /internal/fb/types_arm.go: -------------------------------------------------------------------------------- 1 | // Code generated by cmd/cgo -godefs; DO NOT EDIT. 2 | // cgo -godefs ctypes.go 3 | 4 | package fb 5 | 6 | type FixScreeninfo struct { 7 | Id [16]int8 8 | Smem_start uint32 9 | Smem_len uint32 10 | Type uint32 11 | Type_aux uint32 12 | Visual uint32 13 | Xpanstep uint16 14 | Ypanstep uint16 15 | Ywrapstep uint16 16 | Pad_cgo_0 [2]byte 17 | Line_length uint32 18 | Pad_cgo_1 [4]byte 19 | Mmio_start uint32 20 | Mmio_len uint32 21 | Accel uint32 22 | Capabilities uint16 23 | Reserved [2]uint16 24 | Pad_cgo_2 [2]byte 25 | } 26 | 27 | type Bitfield struct { 28 | Offset uint32 29 | Length uint32 30 | Right uint32 31 | } 32 | 33 | type VarScreeninfo struct { 34 | Xres uint32 35 | Yres uint32 36 | Xres_virtual uint32 37 | Yres_virtual uint32 38 | Xoffset uint32 39 | Yoffset uint32 40 | Bits_per_pixel uint32 41 | Grayscale uint32 42 | Red Bitfield 43 | Green Bitfield 44 | Blue Bitfield 45 | Transp Bitfield 46 | Nonstd uint32 47 | Activate uint32 48 | Height uint32 49 | Width uint32 50 | Accel_flags uint32 51 | Pixclock uint32 52 | Left_margin uint32 53 | Right_margin uint32 54 | Upper_margin uint32 55 | Lower_margin uint32 56 | Hsync_len uint32 57 | Vsync_len uint32 58 | Sync uint32 59 | Vmode uint32 60 | Rotate uint32 61 | Colorspace uint32 62 | Reserved [4]uint32 63 | } 64 | 65 | type Cmap struct { 66 | Start uint32 67 | Len uint32 68 | Red *uint16 69 | Green *uint16 70 | Blue *uint16 71 | Transp *uint16 72 | } 73 | 74 | type Con2fbmap struct { 75 | Console uint32 76 | Framebuffer uint32 77 | } 78 | 79 | type Vblank struct { 80 | Flags uint32 81 | Count uint32 82 | Vcount uint32 83 | Hcount uint32 84 | Reserved [4]uint32 85 | } 86 | 87 | type Copyarea struct { 88 | Dx uint32 89 | Dy uint32 90 | Width uint32 91 | Height uint32 92 | Sx uint32 93 | Sy uint32 94 | } 95 | 96 | type Fillrect struct { 97 | Dx uint32 98 | Dy uint32 99 | Width uint32 100 | Height uint32 101 | Color uint32 102 | Rop uint32 103 | } 104 | 105 | type Image struct { 106 | Dx uint32 107 | Dy uint32 108 | Width uint32 109 | Height uint32 110 | Fg_color uint32 111 | Bg_color uint32 112 | Depth uint8 113 | Pad_cgo_0 [7]byte 114 | Data *int8 115 | Cmap Cmap 116 | } 117 | 118 | type Curpos struct { 119 | X uint16 120 | Y uint16 121 | } 122 | 123 | type Cursor struct { 124 | Set uint16 125 | Enable uint16 126 | Rop uint16 127 | Pad_cgo_0 [2]byte 128 | Mask *int8 129 | Hot Curpos 130 | Pad_cgo_1 [4]byte 131 | Image Image 132 | } 133 | 134 | const ( 135 | FB_MAX = 0x20 136 | FBIOGET_VSCREENINFO = 0x4600 137 | FBIOPUT_VSCREENINFO = 0x4601 138 | FBIOGET_FSCREENINFO = 0x4602 139 | FBIOGETCMAP = 0x4604 140 | FBIOPUTCMAP = 0x4605 141 | FBIOPAN_DISPLAY = 0x4606 142 | FBIO_CURSOR = 0xc0684608 143 | FBIOGET_CON2FBMAP = 0x460f 144 | FBIOPUT_CON2FBMAP = 0x4610 145 | FBIOBLANK = 0x4611 146 | FBIOGET_VBLANK = 0x80204612 147 | FBIO_ALLOC = 0x4613 148 | FBIO_FREE = 0x4614 149 | FBIOGET_GLYPH = 0x4615 150 | FBIOGET_HWCINFO = 0x4616 151 | FBIOPUT_MODEINFO = 0x4617 152 | FBIOGET_DISPINFO = 0x4618 153 | FBIO_WAITFORVSYNC = 0x40044620 154 | FB_TYPE_PACKED_PIXELS = 0x0 155 | FB_TYPE_PLANES = 0x1 156 | FB_TYPE_INTERLEAVED_PLANES = 0x2 157 | FB_TYPE_TEXT = 0x3 158 | FB_TYPE_VGA_PLANES = 0x4 159 | FB_TYPE_FOURCC = 0x5 160 | FB_AUX_TEXT_MDA = 0x0 161 | FB_AUX_TEXT_CGA = 0x1 162 | FB_AUX_TEXT_S3_MMIO = 0x2 163 | FB_AUX_TEXT_MGA_STEP16 = 0x3 164 | FB_AUX_TEXT_MGA_STEP8 = 0x4 165 | FB_AUX_TEXT_SVGA_GROUP = 0x8 166 | FB_AUX_TEXT_SVGA_MASK = 0x7 167 | FB_AUX_TEXT_SVGA_STEP2 = 0x8 168 | FB_AUX_TEXT_SVGA_STEP4 = 0x9 169 | FB_AUX_TEXT_SVGA_STEP8 = 0xa 170 | FB_AUX_TEXT_SVGA_STEP16 = 0xb 171 | FB_AUX_TEXT_SVGA_LAST = 0xf 172 | FB_AUX_VGA_PLANES_VGA4 = 0x0 173 | FB_AUX_VGA_PLANES_CFB4 = 0x1 174 | FB_AUX_VGA_PLANES_CFB8 = 0x2 175 | FB_VISUAL_MONO01 = 0x0 176 | FB_VISUAL_MONO10 = 0x1 177 | FB_VISUAL_TRUECOLOR = 0x2 178 | FB_VISUAL_PSEUDOCOLOR = 0x3 179 | FB_VISUAL_DIRECTCOLOR = 0x4 180 | FB_VISUAL_STATIC_PSEUDOCOLOR = 0x5 181 | FB_VISUAL_FOURCC = 0x6 182 | FB_ACCEL_NONE = 0x0 183 | FB_ACCEL_ATARIBLITT = 0x1 184 | FB_ACCEL_AMIGABLITT = 0x2 185 | FB_ACCEL_S3_TRIO64 = 0x3 186 | FB_ACCEL_NCR_77C32BLT = 0x4 187 | FB_ACCEL_S3_VIRGE = 0x5 188 | FB_ACCEL_ATI_MACH64GX = 0x6 189 | FB_ACCEL_DEC_TGA = 0x7 190 | FB_ACCEL_ATI_MACH64CT = 0x8 191 | FB_ACCEL_ATI_MACH64VT = 0x9 192 | FB_ACCEL_ATI_MACH64GT = 0xa 193 | FB_ACCEL_SUN_CREATOR = 0xb 194 | FB_ACCEL_SUN_CGSIX = 0xc 195 | FB_ACCEL_SUN_LEO = 0xd 196 | FB_ACCEL_IMS_TWINTURBO = 0xe 197 | FB_ACCEL_3DLABS_PERMEDIA2 = 0xf 198 | FB_ACCEL_MATROX_MGA2064W = 0x10 199 | FB_ACCEL_MATROX_MGA1064SG = 0x11 200 | FB_ACCEL_MATROX_MGA2164W = 0x12 201 | FB_ACCEL_MATROX_MGA2164W_AGP = 0x13 202 | FB_ACCEL_MATROX_MGAG100 = 0x14 203 | FB_ACCEL_MATROX_MGAG200 = 0x15 204 | FB_ACCEL_SUN_CG14 = 0x16 205 | FB_ACCEL_SUN_BWTWO = 0x17 206 | FB_ACCEL_SUN_CGTHREE = 0x18 207 | FB_ACCEL_SUN_TCX = 0x19 208 | FB_ACCEL_MATROX_MGAG400 = 0x1a 209 | FB_ACCEL_NV3 = 0x1b 210 | FB_ACCEL_NV4 = 0x1c 211 | FB_ACCEL_NV5 = 0x1d 212 | FB_ACCEL_CT_6555x = 0x1e 213 | FB_ACCEL_3DFX_BANSHEE = 0x1f 214 | FB_ACCEL_ATI_RAGE128 = 0x20 215 | FB_ACCEL_IGS_CYBER2000 = 0x21 216 | FB_ACCEL_IGS_CYBER2010 = 0x22 217 | FB_ACCEL_IGS_CYBER5000 = 0x23 218 | FB_ACCEL_SIS_GLAMOUR = 0x24 219 | FB_ACCEL_3DLABS_PERMEDIA3 = 0x25 220 | FB_ACCEL_ATI_RADEON = 0x26 221 | FB_ACCEL_I810 = 0x27 222 | FB_ACCEL_SIS_GLAMOUR_2 = 0x28 223 | FB_ACCEL_SIS_XABRE = 0x29 224 | FB_ACCEL_I830 = 0x2a 225 | FB_ACCEL_NV_10 = 0x2b 226 | FB_ACCEL_NV_20 = 0x2c 227 | FB_ACCEL_NV_30 = 0x2d 228 | FB_ACCEL_NV_40 = 0x2e 229 | FB_ACCEL_XGI_VOLARI_V = 0x2f 230 | FB_ACCEL_XGI_VOLARI_Z = 0x30 231 | FB_ACCEL_OMAP1610 = 0x31 232 | FB_ACCEL_TRIDENT_TGUI = 0x32 233 | FB_ACCEL_TRIDENT_3DIMAGE = 0x33 234 | FB_ACCEL_TRIDENT_BLADE3D = 0x34 235 | FB_ACCEL_TRIDENT_BLADEXP = 0x35 236 | FB_ACCEL_CIRRUS_ALPINE = 0x35 237 | FB_ACCEL_NEOMAGIC_NM2070 = 0x5a 238 | FB_ACCEL_NEOMAGIC_NM2090 = 0x5b 239 | FB_ACCEL_NEOMAGIC_NM2093 = 0x5c 240 | FB_ACCEL_NEOMAGIC_NM2097 = 0x5d 241 | FB_ACCEL_NEOMAGIC_NM2160 = 0x5e 242 | FB_ACCEL_NEOMAGIC_NM2200 = 0x5f 243 | FB_ACCEL_NEOMAGIC_NM2230 = 0x60 244 | FB_ACCEL_NEOMAGIC_NM2360 = 0x61 245 | FB_ACCEL_NEOMAGIC_NM2380 = 0x62 246 | FB_ACCEL_PXA3XX = 0x63 247 | FB_ACCEL_SAVAGE4 = 0x80 248 | FB_ACCEL_SAVAGE3D = 0x81 249 | FB_ACCEL_SAVAGE3D_MV = 0x82 250 | FB_ACCEL_SAVAGE2000 = 0x83 251 | FB_ACCEL_SAVAGE_MX_MV = 0x84 252 | FB_ACCEL_SAVAGE_MX = 0x85 253 | FB_ACCEL_SAVAGE_IX_MV = 0x86 254 | FB_ACCEL_SAVAGE_IX = 0x87 255 | FB_ACCEL_PROSAVAGE_PM = 0x88 256 | FB_ACCEL_PROSAVAGE_KM = 0x89 257 | FB_ACCEL_S3TWISTER_P = 0x8a 258 | FB_ACCEL_S3TWISTER_K = 0x8b 259 | FB_ACCEL_SUPERSAVAGE = 0x8c 260 | FB_ACCEL_PROSAVAGE_DDR = 0x8d 261 | FB_ACCEL_PROSAVAGE_DDRK = 0x8e 262 | FB_ACCEL_PUV3_UNIGFX = 0xa0 263 | FB_CAP_FOURCC = 0x1 264 | FB_NONSTD_HAM = 0x1 265 | FB_NONSTD_REV_PIX_IN_B = 0x2 266 | FB_ACTIVATE_NOW = 0x0 267 | FB_ACTIVATE_NXTOPEN = 0x1 268 | FB_ACTIVATE_TEST = 0x2 269 | FB_ACTIVATE_MASK = 0xf 270 | FB_ACTIVATE_VBL = 0x10 271 | FB_CHANGE_CMAP_VBL = 0x20 272 | FB_ACTIVATE_ALL = 0x40 273 | FB_ACTIVATE_FORCE = 0x80 274 | FB_ACTIVATE_INV_MODE = 0x100 275 | FB_ACCELF_TEXT = 0x1 276 | FB_SYNC_HOR_HIGH_ACT = 0x1 277 | FB_SYNC_VERT_HIGH_ACT = 0x2 278 | FB_SYNC_EXT = 0x4 279 | FB_SYNC_COMP_HIGH_ACT = 0x8 280 | FB_SYNC_BROADCAST = 0x10 281 | FB_SYNC_ON_GREEN = 0x20 282 | FB_VMODE_NONINTERLACED = 0x0 283 | FB_VMODE_INTERLACED = 0x1 284 | FB_VMODE_DOUBLE = 0x2 285 | FB_VMODE_ODD_FLD_FIRST = 0x4 286 | FB_VMODE_MASK = 0xff 287 | FB_VMODE_YWRAP = 0x100 288 | FB_VMODE_SMOOTH_XPAN = 0x200 289 | FB_VMODE_CONUPDATE = 0x200 290 | FB_ROTATE_UR = 0x0 291 | FB_ROTATE_CW = 0x1 292 | FB_ROTATE_UD = 0x2 293 | FB_ROTATE_CCW = 0x3 294 | VESA_NO_BLANKING = 0x0 295 | VESA_VSYNC_SUSPEND = 0x1 296 | VESA_HSYNC_SUSPEND = 0x2 297 | VESA_POWERDOWN = 0x3 298 | FB_VBLANK_VBLANKING = 0x1 299 | FB_VBLANK_HBLANKING = 0x2 300 | FB_VBLANK_HAVE_VBLANK = 0x4 301 | FB_VBLANK_HAVE_HBLANK = 0x8 302 | FB_VBLANK_HAVE_COUNT = 0x10 303 | FB_VBLANK_HAVE_VCOUNT = 0x20 304 | FB_VBLANK_HAVE_HCOUNT = 0x40 305 | FB_VBLANK_VSYNCING = 0x80 306 | FB_VBLANK_HAVE_VSYNC = 0x100 307 | ROP_COPY = 0x0 308 | ROP_XOR = 0x1 309 | FB_CUR_SETIMAGE = 0x1 310 | FB_CUR_SETPOS = 0x2 311 | FB_CUR_SETHOT = 0x4 312 | FB_CUR_SETCMAP = 0x8 313 | FB_CUR_SETSHAPE = 0x10 314 | FB_CUR_SETSIZE = 0x20 315 | FB_CUR_SETALL = 0xff 316 | FB_BACKLIGHT_LEVELS = 0x80 317 | FB_BACKLIGHT_MAX = 0xff 318 | ) 319 | -------------------------------------------------------------------------------- /internal/png/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /internal/png/paeth.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package png 6 | 7 | // intSize is either 32 or 64. 8 | const intSize = 32 << (^uint(0) >> 63) 9 | 10 | func abs(x int) int { 11 | // m := -1 if x < 0. m := 0 otherwise. 12 | m := x >> (intSize - 1) 13 | 14 | // In two's complement representation, the negative number 15 | // of any number (except the smallest one) can be computed 16 | // by flipping all the bits and add 1. This is faster than 17 | // code with a branch. 18 | // See Hacker's Delight, section 2-4. 19 | return (x ^ m) - m 20 | } 21 | 22 | // paeth implements the Paeth filter function, as per the PNG specification. 23 | func paeth(a, b, c uint8) uint8 { 24 | // This is an optimized version of the sample code in the PNG spec. 25 | // For example, the sample code starts with: 26 | // p := int(a) + int(b) - int(c) 27 | // pa := abs(p - int(a)) 28 | // but the optimized form uses fewer arithmetic operations: 29 | // pa := int(b) - int(c) 30 | // pa = abs(pa) 31 | pc := int(c) 32 | pa := int(b) - pc 33 | pb := int(a) - pc 34 | pc = abs(pa + pb) 35 | pa = abs(pa) 36 | pb = abs(pb) 37 | if pa <= pb && pa <= pc { 38 | return a 39 | } else if pb <= pc { 40 | return b 41 | } 42 | return c 43 | } 44 | 45 | // filterPaeth applies the Paeth filter to the cdat slice. 46 | // cdat is the current row's data, pdat is the previous row's data. 47 | func filterPaeth(cdat, pdat []byte, bytesPerPixel int) { 48 | var a, b, c, pa, pb, pc int 49 | for i := 0; i < bytesPerPixel; i++ { 50 | a, c = 0, 0 51 | for j := i; j < len(cdat); j += bytesPerPixel { 52 | b = int(pdat[j]) 53 | pa = b - c 54 | pb = a - c 55 | pc = abs(pa + pb) 56 | pa = abs(pa) 57 | pb = abs(pb) 58 | if pa <= pb && pa <= pc { 59 | // No-op. 60 | } else if pb <= pc { 61 | a = b 62 | } else { 63 | a = c 64 | } 65 | a += int(cdat[j]) 66 | a &= 0xff 67 | cdat[j] = uint8(a) 68 | c = b 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /internal/png/reader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package png implements a PNG image decoder and encoder. 6 | // 7 | // The PNG specification is at https://www.w3.org/TR/PNG/. 8 | // 9 | // This is a temporary fork of the stdlib png package, containing a patch to 10 | // speed up Gray16 encoding. 11 | package png 12 | 13 | import ( 14 | "compress/zlib" 15 | "encoding/binary" 16 | "fmt" 17 | "hash" 18 | "hash/crc32" 19 | "image" 20 | "image/color" 21 | "io" 22 | ) 23 | 24 | // Color type, as per the PNG spec. 25 | const ( 26 | ctGrayscale = 0 27 | ctTrueColor = 2 28 | ctPaletted = 3 29 | ctGrayscaleAlpha = 4 30 | ctTrueColorAlpha = 6 31 | ) 32 | 33 | // A cb is a combination of color type and bit depth. 34 | const ( 35 | cbInvalid = iota 36 | cbG1 37 | cbG2 38 | cbG4 39 | cbG8 40 | cbGA8 41 | cbTC8 42 | cbP1 43 | cbP2 44 | cbP4 45 | cbP8 46 | cbTCA8 47 | cbG16 48 | cbGA16 49 | cbTC16 50 | cbTCA16 51 | ) 52 | 53 | func cbPaletted(cb int) bool { 54 | return cbP1 <= cb && cb <= cbP8 55 | } 56 | 57 | // Filter type, as per the PNG spec. 58 | const ( 59 | ftNone = 0 60 | ftSub = 1 61 | ftUp = 2 62 | ftAverage = 3 63 | ftPaeth = 4 64 | nFilter = 5 65 | ) 66 | 67 | // Interlace type. 68 | const ( 69 | itNone = 0 70 | itAdam7 = 1 71 | ) 72 | 73 | // interlaceScan defines the placement and size of a pass for Adam7 interlacing. 74 | type interlaceScan struct { 75 | xFactor, yFactor, xOffset, yOffset int 76 | } 77 | 78 | // interlacing defines Adam7 interlacing, with 7 passes of reduced images. 79 | // See https://www.w3.org/TR/PNG/#8Interlace 80 | var interlacing = []interlaceScan{ 81 | {8, 8, 0, 0}, 82 | {8, 8, 4, 0}, 83 | {4, 8, 0, 4}, 84 | {4, 4, 2, 0}, 85 | {2, 4, 0, 2}, 86 | {2, 2, 1, 0}, 87 | {1, 2, 0, 1}, 88 | } 89 | 90 | // Decoding stage. 91 | // The PNG specification says that the IHDR, PLTE (if present), tRNS (if 92 | // present), IDAT and IEND chunks must appear in that order. There may be 93 | // multiple IDAT chunks, and IDAT chunks must be sequential (i.e. they may not 94 | // have any other chunks between them). 95 | // https://www.w3.org/TR/PNG/#5ChunkOrdering 96 | const ( 97 | dsStart = iota 98 | dsSeenIHDR 99 | dsSeenPLTE 100 | dsSeentRNS 101 | dsSeenIDAT 102 | dsSeenIEND 103 | ) 104 | 105 | const pngHeader = "\x89PNG\r\n\x1a\n" 106 | 107 | type decoder struct { 108 | r io.Reader 109 | img image.Image 110 | crc hash.Hash32 111 | width, height int 112 | depth int 113 | palette color.Palette 114 | cb int 115 | stage int 116 | idatLength uint32 117 | tmp [3 * 256]byte 118 | interlace int 119 | 120 | // useTransparent and transparent are used for grayscale and truecolor 121 | // transparency, as opposed to palette transparency. 122 | useTransparent bool 123 | transparent [6]byte 124 | } 125 | 126 | // A FormatError reports that the input is not a valid PNG. 127 | type FormatError string 128 | 129 | func (e FormatError) Error() string { return "png: invalid format: " + string(e) } 130 | 131 | var chunkOrderError = FormatError("chunk out of order") 132 | 133 | // An UnsupportedError reports that the input uses a valid but unimplemented PNG feature. 134 | type UnsupportedError string 135 | 136 | func (e UnsupportedError) Error() string { return "png: unsupported feature: " + string(e) } 137 | 138 | func min(a, b int) int { 139 | if a < b { 140 | return a 141 | } 142 | return b 143 | } 144 | 145 | func (d *decoder) parseIHDR(length uint32) error { 146 | if length != 13 { 147 | return FormatError("bad IHDR length") 148 | } 149 | if _, err := io.ReadFull(d.r, d.tmp[:13]); err != nil { 150 | return err 151 | } 152 | d.crc.Write(d.tmp[:13]) 153 | if d.tmp[10] != 0 { 154 | return UnsupportedError("compression method") 155 | } 156 | if d.tmp[11] != 0 { 157 | return UnsupportedError("filter method") 158 | } 159 | if d.tmp[12] != itNone && d.tmp[12] != itAdam7 { 160 | return FormatError("invalid interlace method") 161 | } 162 | d.interlace = int(d.tmp[12]) 163 | 164 | w := int32(binary.BigEndian.Uint32(d.tmp[0:4])) 165 | h := int32(binary.BigEndian.Uint32(d.tmp[4:8])) 166 | if w <= 0 || h <= 0 { 167 | return FormatError("non-positive dimension") 168 | } 169 | nPixels := int64(w) * int64(h) 170 | if nPixels != int64(int(nPixels)) { 171 | return UnsupportedError("dimension overflow") 172 | } 173 | // There can be up to 8 bytes per pixel, for 16 bits per channel RGBA. 174 | if nPixels != (nPixels*8)/8 { 175 | return UnsupportedError("dimension overflow") 176 | } 177 | 178 | d.cb = cbInvalid 179 | d.depth = int(d.tmp[8]) 180 | switch d.depth { 181 | case 1: 182 | switch d.tmp[9] { 183 | case ctGrayscale: 184 | d.cb = cbG1 185 | case ctPaletted: 186 | d.cb = cbP1 187 | } 188 | case 2: 189 | switch d.tmp[9] { 190 | case ctGrayscale: 191 | d.cb = cbG2 192 | case ctPaletted: 193 | d.cb = cbP2 194 | } 195 | case 4: 196 | switch d.tmp[9] { 197 | case ctGrayscale: 198 | d.cb = cbG4 199 | case ctPaletted: 200 | d.cb = cbP4 201 | } 202 | case 8: 203 | switch d.tmp[9] { 204 | case ctGrayscale: 205 | d.cb = cbG8 206 | case ctTrueColor: 207 | d.cb = cbTC8 208 | case ctPaletted: 209 | d.cb = cbP8 210 | case ctGrayscaleAlpha: 211 | d.cb = cbGA8 212 | case ctTrueColorAlpha: 213 | d.cb = cbTCA8 214 | } 215 | case 16: 216 | switch d.tmp[9] { 217 | case ctGrayscale: 218 | d.cb = cbG16 219 | case ctTrueColor: 220 | d.cb = cbTC16 221 | case ctGrayscaleAlpha: 222 | d.cb = cbGA16 223 | case ctTrueColorAlpha: 224 | d.cb = cbTCA16 225 | } 226 | } 227 | if d.cb == cbInvalid { 228 | return UnsupportedError(fmt.Sprintf("bit depth %d, color type %d", d.tmp[8], d.tmp[9])) 229 | } 230 | d.width, d.height = int(w), int(h) 231 | return d.verifyChecksum() 232 | } 233 | 234 | func (d *decoder) parsePLTE(length uint32) error { 235 | np := int(length / 3) // The number of palette entries. 236 | if length%3 != 0 || np <= 0 || np > 256 || np > 1< 256 { 306 | return FormatError("bad tRNS length") 307 | } 308 | n, err := io.ReadFull(d.r, d.tmp[:length]) 309 | if err != nil { 310 | return err 311 | } 312 | d.crc.Write(d.tmp[:n]) 313 | 314 | if len(d.palette) < n { 315 | d.palette = d.palette[:n] 316 | } 317 | for i := 0; i < n; i++ { 318 | rgba := d.palette[i].(color.RGBA) 319 | d.palette[i] = color.NRGBA{rgba.R, rgba.G, rgba.B, d.tmp[i]} 320 | } 321 | 322 | default: 323 | return FormatError("tRNS, color type mismatch") 324 | } 325 | return d.verifyChecksum() 326 | } 327 | 328 | // Read presents one or more IDAT chunks as one continuous stream (minus the 329 | // intermediate chunk headers and footers). If the PNG data looked like: 330 | // ... len0 IDAT xxx crc0 len1 IDAT yy crc1 len2 IEND crc2 331 | // then this reader presents xxxyy. For well-formed PNG data, the decoder state 332 | // immediately before the first Read call is that d.r is positioned between the 333 | // first IDAT and xxx, and the decoder state immediately after the last Read 334 | // call is that d.r is positioned between yy and crc1. 335 | func (d *decoder) Read(p []byte) (int, error) { 336 | if len(p) == 0 { 337 | return 0, nil 338 | } 339 | for d.idatLength == 0 { 340 | // We have exhausted an IDAT chunk. Verify the checksum of that chunk. 341 | if err := d.verifyChecksum(); err != nil { 342 | return 0, err 343 | } 344 | // Read the length and chunk type of the next chunk, and check that 345 | // it is an IDAT chunk. 346 | if _, err := io.ReadFull(d.r, d.tmp[:8]); err != nil { 347 | return 0, err 348 | } 349 | d.idatLength = binary.BigEndian.Uint32(d.tmp[:4]) 350 | if string(d.tmp[4:8]) != "IDAT" { 351 | return 0, FormatError("not enough pixel data") 352 | } 353 | d.crc.Reset() 354 | d.crc.Write(d.tmp[4:8]) 355 | } 356 | if int(d.idatLength) < 0 { 357 | return 0, UnsupportedError("IDAT chunk length overflow") 358 | } 359 | n, err := d.r.Read(p[:min(len(p), int(d.idatLength))]) 360 | d.crc.Write(p[:n]) 361 | d.idatLength -= uint32(n) 362 | return n, err 363 | } 364 | 365 | // decode decodes the IDAT data into an image. 366 | func (d *decoder) decode() (image.Image, error) { 367 | r, err := zlib.NewReader(d) 368 | if err != nil { 369 | return nil, err 370 | } 371 | defer r.Close() 372 | var img image.Image 373 | if d.interlace == itNone { 374 | img, err = d.readImagePass(r, 0, false) 375 | if err != nil { 376 | return nil, err 377 | } 378 | } else if d.interlace == itAdam7 { 379 | // Allocate a blank image of the full size. 380 | img, err = d.readImagePass(nil, 0, true) 381 | if err != nil { 382 | return nil, err 383 | } 384 | for pass := 0; pass < 7; pass++ { 385 | imagePass, err := d.readImagePass(r, pass, false) 386 | if err != nil { 387 | return nil, err 388 | } 389 | if imagePass != nil { 390 | d.mergePassInto(img, imagePass, pass) 391 | } 392 | } 393 | } 394 | 395 | // Check for EOF, to verify the zlib checksum. 396 | n := 0 397 | for i := 0; n == 0 && err == nil; i++ { 398 | if i == 100 { 399 | return nil, io.ErrNoProgress 400 | } 401 | n, err = r.Read(d.tmp[:1]) 402 | } 403 | if err != nil && err != io.EOF { 404 | return nil, FormatError(err.Error()) 405 | } 406 | if n != 0 || d.idatLength != 0 { 407 | return nil, FormatError("too much pixel data") 408 | } 409 | 410 | return img, nil 411 | } 412 | 413 | // readImagePass reads a single image pass, sized according to the pass number. 414 | func (d *decoder) readImagePass(r io.Reader, pass int, allocateOnly bool) (image.Image, error) { 415 | bitsPerPixel := 0 416 | pixOffset := 0 417 | var ( 418 | gray *image.Gray 419 | rgba *image.RGBA 420 | paletted *image.Paletted 421 | nrgba *image.NRGBA 422 | gray16 *image.Gray16 423 | rgba64 *image.RGBA64 424 | nrgba64 *image.NRGBA64 425 | img image.Image 426 | ) 427 | width, height := d.width, d.height 428 | if d.interlace == itAdam7 && !allocateOnly { 429 | p := interlacing[pass] 430 | // Add the multiplication factor and subtract one, effectively rounding up. 431 | width = (width - p.xOffset + p.xFactor - 1) / p.xFactor 432 | height = (height - p.yOffset + p.yFactor - 1) / p.yFactor 433 | // A PNG image can't have zero width or height, but for an interlaced 434 | // image, an individual pass might have zero width or height. If so, we 435 | // shouldn't even read a per-row filter type byte, so return early. 436 | if width == 0 || height == 0 { 437 | return nil, nil 438 | } 439 | } 440 | switch d.cb { 441 | case cbG1, cbG2, cbG4, cbG8: 442 | bitsPerPixel = d.depth 443 | if d.useTransparent { 444 | nrgba = image.NewNRGBA(image.Rect(0, 0, width, height)) 445 | img = nrgba 446 | } else { 447 | gray = image.NewGray(image.Rect(0, 0, width, height)) 448 | img = gray 449 | } 450 | case cbGA8: 451 | bitsPerPixel = 16 452 | nrgba = image.NewNRGBA(image.Rect(0, 0, width, height)) 453 | img = nrgba 454 | case cbTC8: 455 | bitsPerPixel = 24 456 | if d.useTransparent { 457 | nrgba = image.NewNRGBA(image.Rect(0, 0, width, height)) 458 | img = nrgba 459 | } else { 460 | rgba = image.NewRGBA(image.Rect(0, 0, width, height)) 461 | img = rgba 462 | } 463 | case cbP1, cbP2, cbP4, cbP8: 464 | bitsPerPixel = d.depth 465 | paletted = image.NewPaletted(image.Rect(0, 0, width, height), d.palette) 466 | img = paletted 467 | case cbTCA8: 468 | bitsPerPixel = 32 469 | nrgba = image.NewNRGBA(image.Rect(0, 0, width, height)) 470 | img = nrgba 471 | case cbG16: 472 | bitsPerPixel = 16 473 | if d.useTransparent { 474 | nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height)) 475 | img = nrgba64 476 | } else { 477 | gray16 = image.NewGray16(image.Rect(0, 0, width, height)) 478 | img = gray16 479 | } 480 | case cbGA16: 481 | bitsPerPixel = 32 482 | nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height)) 483 | img = nrgba64 484 | case cbTC16: 485 | bitsPerPixel = 48 486 | if d.useTransparent { 487 | nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height)) 488 | img = nrgba64 489 | } else { 490 | rgba64 = image.NewRGBA64(image.Rect(0, 0, width, height)) 491 | img = rgba64 492 | } 493 | case cbTCA16: 494 | bitsPerPixel = 64 495 | nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height)) 496 | img = nrgba64 497 | } 498 | if allocateOnly { 499 | return img, nil 500 | } 501 | bytesPerPixel := (bitsPerPixel + 7) / 8 502 | 503 | // The +1 is for the per-row filter type, which is at cr[0]. 504 | rowSize := 1 + (bitsPerPixel*width+7)/8 505 | // cr and pr are the bytes for the current and previous row. 506 | cr := make([]uint8, rowSize) 507 | pr := make([]uint8, rowSize) 508 | 509 | for y := 0; y < height; y++ { 510 | // Read the decompressed bytes. 511 | _, err := io.ReadFull(r, cr) 512 | if err != nil { 513 | if err == io.EOF || err == io.ErrUnexpectedEOF { 514 | return nil, FormatError("not enough pixel data") 515 | } 516 | return nil, err 517 | } 518 | 519 | // Apply the filter. 520 | cdat := cr[1:] 521 | pdat := pr[1:] 522 | switch cr[0] { 523 | case ftNone: 524 | // No-op. 525 | case ftSub: 526 | for i := bytesPerPixel; i < len(cdat); i++ { 527 | cdat[i] += cdat[i-bytesPerPixel] 528 | } 529 | case ftUp: 530 | for i, p := range pdat { 531 | cdat[i] += p 532 | } 533 | case ftAverage: 534 | // The first column has no column to the left of it, so it is a 535 | // special case. We know that the first column exists because we 536 | // check above that width != 0, and so len(cdat) != 0. 537 | for i := 0; i < bytesPerPixel; i++ { 538 | cdat[i] += pdat[i] / 2 539 | } 540 | for i := bytesPerPixel; i < len(cdat); i++ { 541 | cdat[i] += uint8((int(cdat[i-bytesPerPixel]) + int(pdat[i])) / 2) 542 | } 543 | case ftPaeth: 544 | filterPaeth(cdat, pdat, bytesPerPixel) 545 | default: 546 | return nil, FormatError("bad filter type") 547 | } 548 | 549 | // Convert from bytes to colors. 550 | switch d.cb { 551 | case cbG1: 552 | if d.useTransparent { 553 | ty := d.transparent[1] 554 | for x := 0; x < width; x += 8 { 555 | b := cdat[x/8] 556 | for x2 := 0; x2 < 8 && x+x2 < width; x2++ { 557 | ycol := (b >> 7) * 0xff 558 | acol := uint8(0xff) 559 | if ycol == ty { 560 | acol = 0x00 561 | } 562 | nrgba.SetNRGBA(x+x2, y, color.NRGBA{ycol, ycol, ycol, acol}) 563 | b <<= 1 564 | } 565 | } 566 | } else { 567 | for x := 0; x < width; x += 8 { 568 | b := cdat[x/8] 569 | for x2 := 0; x2 < 8 && x+x2 < width; x2++ { 570 | gray.SetGray(x+x2, y, color.Gray{(b >> 7) * 0xff}) 571 | b <<= 1 572 | } 573 | } 574 | } 575 | case cbG2: 576 | if d.useTransparent { 577 | ty := d.transparent[1] 578 | for x := 0; x < width; x += 4 { 579 | b := cdat[x/4] 580 | for x2 := 0; x2 < 4 && x+x2 < width; x2++ { 581 | ycol := (b >> 6) * 0x55 582 | acol := uint8(0xff) 583 | if ycol == ty { 584 | acol = 0x00 585 | } 586 | nrgba.SetNRGBA(x+x2, y, color.NRGBA{ycol, ycol, ycol, acol}) 587 | b <<= 2 588 | } 589 | } 590 | } else { 591 | for x := 0; x < width; x += 4 { 592 | b := cdat[x/4] 593 | for x2 := 0; x2 < 4 && x+x2 < width; x2++ { 594 | gray.SetGray(x+x2, y, color.Gray{(b >> 6) * 0x55}) 595 | b <<= 2 596 | } 597 | } 598 | } 599 | case cbG4: 600 | if d.useTransparent { 601 | ty := d.transparent[1] 602 | for x := 0; x < width; x += 2 { 603 | b := cdat[x/2] 604 | for x2 := 0; x2 < 2 && x+x2 < width; x2++ { 605 | ycol := (b >> 4) * 0x11 606 | acol := uint8(0xff) 607 | if ycol == ty { 608 | acol = 0x00 609 | } 610 | nrgba.SetNRGBA(x+x2, y, color.NRGBA{ycol, ycol, ycol, acol}) 611 | b <<= 4 612 | } 613 | } 614 | } else { 615 | for x := 0; x < width; x += 2 { 616 | b := cdat[x/2] 617 | for x2 := 0; x2 < 2 && x+x2 < width; x2++ { 618 | gray.SetGray(x+x2, y, color.Gray{(b >> 4) * 0x11}) 619 | b <<= 4 620 | } 621 | } 622 | } 623 | case cbG8: 624 | if d.useTransparent { 625 | ty := d.transparent[1] 626 | for x := 0; x < width; x++ { 627 | ycol := cdat[x] 628 | acol := uint8(0xff) 629 | if ycol == ty { 630 | acol = 0x00 631 | } 632 | nrgba.SetNRGBA(x, y, color.NRGBA{ycol, ycol, ycol, acol}) 633 | } 634 | } else { 635 | copy(gray.Pix[pixOffset:], cdat) 636 | pixOffset += gray.Stride 637 | } 638 | case cbGA8: 639 | for x := 0; x < width; x++ { 640 | ycol := cdat[2*x+0] 641 | nrgba.SetNRGBA(x, y, color.NRGBA{ycol, ycol, ycol, cdat[2*x+1]}) 642 | } 643 | case cbTC8: 644 | if d.useTransparent { 645 | pix, i, j := nrgba.Pix, pixOffset, 0 646 | tr, tg, tb := d.transparent[1], d.transparent[3], d.transparent[5] 647 | for x := 0; x < width; x++ { 648 | r := cdat[j+0] 649 | g := cdat[j+1] 650 | b := cdat[j+2] 651 | a := uint8(0xff) 652 | if r == tr && g == tg && b == tb { 653 | a = 0x00 654 | } 655 | pix[i+0] = r 656 | pix[i+1] = g 657 | pix[i+2] = b 658 | pix[i+3] = a 659 | i += 4 660 | j += 3 661 | } 662 | pixOffset += nrgba.Stride 663 | } else { 664 | pix, i, j := rgba.Pix, pixOffset, 0 665 | for x := 0; x < width; x++ { 666 | pix[i+0] = cdat[j+0] 667 | pix[i+1] = cdat[j+1] 668 | pix[i+2] = cdat[j+2] 669 | pix[i+3] = 0xff 670 | i += 4 671 | j += 3 672 | } 673 | pixOffset += rgba.Stride 674 | } 675 | case cbP1: 676 | for x := 0; x < width; x += 8 { 677 | b := cdat[x/8] 678 | for x2 := 0; x2 < 8 && x+x2 < width; x2++ { 679 | idx := b >> 7 680 | if len(paletted.Palette) <= int(idx) { 681 | paletted.Palette = paletted.Palette[:int(idx)+1] 682 | } 683 | paletted.SetColorIndex(x+x2, y, idx) 684 | b <<= 1 685 | } 686 | } 687 | case cbP2: 688 | for x := 0; x < width; x += 4 { 689 | b := cdat[x/4] 690 | for x2 := 0; x2 < 4 && x+x2 < width; x2++ { 691 | idx := b >> 6 692 | if len(paletted.Palette) <= int(idx) { 693 | paletted.Palette = paletted.Palette[:int(idx)+1] 694 | } 695 | paletted.SetColorIndex(x+x2, y, idx) 696 | b <<= 2 697 | } 698 | } 699 | case cbP4: 700 | for x := 0; x < width; x += 2 { 701 | b := cdat[x/2] 702 | for x2 := 0; x2 < 2 && x+x2 < width; x2++ { 703 | idx := b >> 4 704 | if len(paletted.Palette) <= int(idx) { 705 | paletted.Palette = paletted.Palette[:int(idx)+1] 706 | } 707 | paletted.SetColorIndex(x+x2, y, idx) 708 | b <<= 4 709 | } 710 | } 711 | case cbP8: 712 | if len(paletted.Palette) != 255 { 713 | for x := 0; x < width; x++ { 714 | if len(paletted.Palette) <= int(cdat[x]) { 715 | paletted.Palette = paletted.Palette[:int(cdat[x])+1] 716 | } 717 | } 718 | } 719 | copy(paletted.Pix[pixOffset:], cdat) 720 | pixOffset += paletted.Stride 721 | case cbTCA8: 722 | copy(nrgba.Pix[pixOffset:], cdat) 723 | pixOffset += nrgba.Stride 724 | case cbG16: 725 | if d.useTransparent { 726 | ty := uint16(d.transparent[0])<<8 | uint16(d.transparent[1]) 727 | for x := 0; x < width; x++ { 728 | ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1]) 729 | acol := uint16(0xffff) 730 | if ycol == ty { 731 | acol = 0x0000 732 | } 733 | nrgba64.SetNRGBA64(x, y, color.NRGBA64{ycol, ycol, ycol, acol}) 734 | } 735 | } else { 736 | for x := 0; x < width; x++ { 737 | ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1]) 738 | gray16.SetGray16(x, y, color.Gray16{ycol}) 739 | } 740 | } 741 | case cbGA16: 742 | for x := 0; x < width; x++ { 743 | ycol := uint16(cdat[4*x+0])<<8 | uint16(cdat[4*x+1]) 744 | acol := uint16(cdat[4*x+2])<<8 | uint16(cdat[4*x+3]) 745 | nrgba64.SetNRGBA64(x, y, color.NRGBA64{ycol, ycol, ycol, acol}) 746 | } 747 | case cbTC16: 748 | if d.useTransparent { 749 | tr := uint16(d.transparent[0])<<8 | uint16(d.transparent[1]) 750 | tg := uint16(d.transparent[2])<<8 | uint16(d.transparent[3]) 751 | tb := uint16(d.transparent[4])<<8 | uint16(d.transparent[5]) 752 | for x := 0; x < width; x++ { 753 | rcol := uint16(cdat[6*x+0])<<8 | uint16(cdat[6*x+1]) 754 | gcol := uint16(cdat[6*x+2])<<8 | uint16(cdat[6*x+3]) 755 | bcol := uint16(cdat[6*x+4])<<8 | uint16(cdat[6*x+5]) 756 | acol := uint16(0xffff) 757 | if rcol == tr && gcol == tg && bcol == tb { 758 | acol = 0x0000 759 | } 760 | nrgba64.SetNRGBA64(x, y, color.NRGBA64{rcol, gcol, bcol, acol}) 761 | } 762 | } else { 763 | for x := 0; x < width; x++ { 764 | rcol := uint16(cdat[6*x+0])<<8 | uint16(cdat[6*x+1]) 765 | gcol := uint16(cdat[6*x+2])<<8 | uint16(cdat[6*x+3]) 766 | bcol := uint16(cdat[6*x+4])<<8 | uint16(cdat[6*x+5]) 767 | rgba64.SetRGBA64(x, y, color.RGBA64{rcol, gcol, bcol, 0xffff}) 768 | } 769 | } 770 | case cbTCA16: 771 | for x := 0; x < width; x++ { 772 | rcol := uint16(cdat[8*x+0])<<8 | uint16(cdat[8*x+1]) 773 | gcol := uint16(cdat[8*x+2])<<8 | uint16(cdat[8*x+3]) 774 | bcol := uint16(cdat[8*x+4])<<8 | uint16(cdat[8*x+5]) 775 | acol := uint16(cdat[8*x+6])<<8 | uint16(cdat[8*x+7]) 776 | nrgba64.SetNRGBA64(x, y, color.NRGBA64{rcol, gcol, bcol, acol}) 777 | } 778 | } 779 | 780 | // The current row for y is the previous row for y+1. 781 | pr, cr = cr, pr 782 | } 783 | 784 | return img, nil 785 | } 786 | 787 | // mergePassInto merges a single pass into a full sized image. 788 | func (d *decoder) mergePassInto(dst image.Image, src image.Image, pass int) { 789 | p := interlacing[pass] 790 | var ( 791 | srcPix []uint8 792 | dstPix []uint8 793 | stride int 794 | rect image.Rectangle 795 | bytesPerPixel int 796 | ) 797 | switch target := dst.(type) { 798 | case *image.Alpha: 799 | srcPix = src.(*image.Alpha).Pix 800 | dstPix, stride, rect = target.Pix, target.Stride, target.Rect 801 | bytesPerPixel = 1 802 | case *image.Alpha16: 803 | srcPix = src.(*image.Alpha16).Pix 804 | dstPix, stride, rect = target.Pix, target.Stride, target.Rect 805 | bytesPerPixel = 2 806 | case *image.Gray: 807 | srcPix = src.(*image.Gray).Pix 808 | dstPix, stride, rect = target.Pix, target.Stride, target.Rect 809 | bytesPerPixel = 1 810 | case *image.Gray16: 811 | srcPix = src.(*image.Gray16).Pix 812 | dstPix, stride, rect = target.Pix, target.Stride, target.Rect 813 | bytesPerPixel = 2 814 | case *image.NRGBA: 815 | srcPix = src.(*image.NRGBA).Pix 816 | dstPix, stride, rect = target.Pix, target.Stride, target.Rect 817 | bytesPerPixel = 4 818 | case *image.NRGBA64: 819 | srcPix = src.(*image.NRGBA64).Pix 820 | dstPix, stride, rect = target.Pix, target.Stride, target.Rect 821 | bytesPerPixel = 8 822 | case *image.Paletted: 823 | srcPix = src.(*image.Paletted).Pix 824 | dstPix, stride, rect = target.Pix, target.Stride, target.Rect 825 | bytesPerPixel = 1 826 | case *image.RGBA: 827 | srcPix = src.(*image.RGBA).Pix 828 | dstPix, stride, rect = target.Pix, target.Stride, target.Rect 829 | bytesPerPixel = 4 830 | case *image.RGBA64: 831 | srcPix = src.(*image.RGBA64).Pix 832 | dstPix, stride, rect = target.Pix, target.Stride, target.Rect 833 | bytesPerPixel = 8 834 | } 835 | s, bounds := 0, src.Bounds() 836 | for y := bounds.Min.Y; y < bounds.Max.Y; y++ { 837 | dBase := (y*p.yFactor+p.yOffset-rect.Min.Y)*stride + (p.xOffset-rect.Min.X)*bytesPerPixel 838 | for x := bounds.Min.X; x < bounds.Max.X; x++ { 839 | d := dBase + x*p.xFactor*bytesPerPixel 840 | copy(dstPix[d:], srcPix[s:s+bytesPerPixel]) 841 | s += bytesPerPixel 842 | } 843 | } 844 | } 845 | 846 | func (d *decoder) parseIDAT(length uint32) (err error) { 847 | d.idatLength = length 848 | d.img, err = d.decode() 849 | if err != nil { 850 | return err 851 | } 852 | return d.verifyChecksum() 853 | } 854 | 855 | func (d *decoder) parseIEND(length uint32) error { 856 | if length != 0 { 857 | return FormatError("bad IEND length") 858 | } 859 | return d.verifyChecksum() 860 | } 861 | 862 | func (d *decoder) parseChunk() error { 863 | // Read the length and chunk type. 864 | n, err := io.ReadFull(d.r, d.tmp[:8]) 865 | if err != nil { 866 | return err 867 | } 868 | length := binary.BigEndian.Uint32(d.tmp[:4]) 869 | d.crc.Reset() 870 | d.crc.Write(d.tmp[4:8]) 871 | 872 | // Read the chunk data. 873 | switch string(d.tmp[4:8]) { 874 | case "IHDR": 875 | if d.stage != dsStart { 876 | return chunkOrderError 877 | } 878 | d.stage = dsSeenIHDR 879 | return d.parseIHDR(length) 880 | case "PLTE": 881 | if d.stage != dsSeenIHDR { 882 | return chunkOrderError 883 | } 884 | d.stage = dsSeenPLTE 885 | return d.parsePLTE(length) 886 | case "tRNS": 887 | if cbPaletted(d.cb) { 888 | if d.stage != dsSeenPLTE { 889 | return chunkOrderError 890 | } 891 | } else if d.stage != dsSeenIHDR { 892 | return chunkOrderError 893 | } 894 | d.stage = dsSeentRNS 895 | return d.parsetRNS(length) 896 | case "IDAT": 897 | if d.stage < dsSeenIHDR || d.stage > dsSeenIDAT || (d.stage == dsSeenIHDR && cbPaletted(d.cb)) { 898 | return chunkOrderError 899 | } else if d.stage == dsSeenIDAT { 900 | // Ignore trailing zero-length or garbage IDAT chunks. 901 | // 902 | // This does not affect valid PNG images that contain multiple IDAT 903 | // chunks, since the first call to parseIDAT below will consume all 904 | // consecutive IDAT chunks required for decoding the image. 905 | break 906 | } 907 | d.stage = dsSeenIDAT 908 | return d.parseIDAT(length) 909 | case "IEND": 910 | if d.stage != dsSeenIDAT { 911 | return chunkOrderError 912 | } 913 | d.stage = dsSeenIEND 914 | return d.parseIEND(length) 915 | } 916 | if length > 0x7fffffff { 917 | return FormatError(fmt.Sprintf("Bad chunk length: %d", length)) 918 | } 919 | // Ignore this chunk (of a known length). 920 | var ignored [4096]byte 921 | for length > 0 { 922 | n, err = io.ReadFull(d.r, ignored[:min(len(ignored), int(length))]) 923 | if err != nil { 924 | return err 925 | } 926 | d.crc.Write(ignored[:n]) 927 | length -= uint32(n) 928 | } 929 | return d.verifyChecksum() 930 | } 931 | 932 | func (d *decoder) verifyChecksum() error { 933 | if _, err := io.ReadFull(d.r, d.tmp[:4]); err != nil { 934 | return err 935 | } 936 | if binary.BigEndian.Uint32(d.tmp[:4]) != d.crc.Sum32() { 937 | return FormatError("invalid checksum") 938 | } 939 | return nil 940 | } 941 | 942 | func (d *decoder) checkHeader() error { 943 | _, err := io.ReadFull(d.r, d.tmp[:len(pngHeader)]) 944 | if err != nil { 945 | return err 946 | } 947 | if string(d.tmp[:len(pngHeader)]) != pngHeader { 948 | return FormatError("not a PNG file") 949 | } 950 | return nil 951 | } 952 | 953 | // Decode reads a PNG image from r and returns it as an image.Image. 954 | // The type of Image returned depends on the PNG contents. 955 | func Decode(r io.Reader) (image.Image, error) { 956 | d := &decoder{ 957 | r: r, 958 | crc: crc32.NewIEEE(), 959 | } 960 | if err := d.checkHeader(); err != nil { 961 | if err == io.EOF { 962 | err = io.ErrUnexpectedEOF 963 | } 964 | return nil, err 965 | } 966 | for d.stage != dsSeenIEND { 967 | if err := d.parseChunk(); err != nil { 968 | if err == io.EOF { 969 | err = io.ErrUnexpectedEOF 970 | } 971 | return nil, err 972 | } 973 | } 974 | return d.img, nil 975 | } 976 | 977 | // DecodeConfig returns the color model and dimensions of a PNG image without 978 | // decoding the entire image. 979 | func DecodeConfig(r io.Reader) (image.Config, error) { 980 | d := &decoder{ 981 | r: r, 982 | crc: crc32.NewIEEE(), 983 | } 984 | if err := d.checkHeader(); err != nil { 985 | if err == io.EOF { 986 | err = io.ErrUnexpectedEOF 987 | } 988 | return image.Config{}, err 989 | } 990 | for { 991 | if err := d.parseChunk(); err != nil { 992 | if err == io.EOF { 993 | err = io.ErrUnexpectedEOF 994 | } 995 | return image.Config{}, err 996 | } 997 | paletted := cbPaletted(d.cb) 998 | if d.stage == dsSeenIHDR && !paletted { 999 | break 1000 | } 1001 | if d.stage == dsSeenPLTE && paletted { 1002 | break 1003 | } 1004 | } 1005 | var cm color.Model 1006 | switch d.cb { 1007 | case cbG1, cbG2, cbG4, cbG8: 1008 | cm = color.GrayModel 1009 | case cbGA8: 1010 | cm = color.NRGBAModel 1011 | case cbTC8: 1012 | cm = color.RGBAModel 1013 | case cbP1, cbP2, cbP4, cbP8: 1014 | cm = d.palette 1015 | case cbTCA8: 1016 | cm = color.NRGBAModel 1017 | case cbG16: 1018 | cm = color.Gray16Model 1019 | case cbGA16: 1020 | cm = color.NRGBA64Model 1021 | case cbTC16: 1022 | cm = color.RGBA64Model 1023 | case cbTCA16: 1024 | cm = color.NRGBA64Model 1025 | } 1026 | return image.Config{ 1027 | ColorModel: cm, 1028 | Width: d.width, 1029 | Height: d.height, 1030 | }, nil 1031 | } 1032 | 1033 | func init() { 1034 | image.RegisterFormat("png", pngHeader, Decode, DecodeConfig) 1035 | } 1036 | -------------------------------------------------------------------------------- /internal/png/writer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package png 6 | 7 | import ( 8 | "bufio" 9 | "compress/zlib" 10 | "encoding/binary" 11 | "hash/crc32" 12 | "image" 13 | "image/color" 14 | "io" 15 | "strconv" 16 | ) 17 | 18 | // Encoder configures encoding PNG images. 19 | type Encoder struct { 20 | CompressionLevel CompressionLevel 21 | 22 | // BufferPool optionally specifies a buffer pool to get temporary 23 | // EncoderBuffers when encoding an image. 24 | BufferPool EncoderBufferPool 25 | } 26 | 27 | // EncoderBufferPool is an interface for getting and returning temporary 28 | // instances of the EncoderBuffer struct. This can be used to reuse buffers 29 | // when encoding multiple images. 30 | type EncoderBufferPool interface { 31 | Get() *EncoderBuffer 32 | Put(*EncoderBuffer) 33 | } 34 | 35 | // EncoderBuffer holds the buffers used for encoding PNG images. 36 | type EncoderBuffer encoder 37 | 38 | type encoder struct { 39 | enc *Encoder 40 | w io.Writer 41 | m image.Image 42 | cb int 43 | err error 44 | header [8]byte 45 | footer [4]byte 46 | tmp [4 * 256]byte 47 | cr [nFilter][]uint8 48 | pr []uint8 49 | zw *zlib.Writer 50 | zwLevel int 51 | bw *bufio.Writer 52 | } 53 | 54 | type CompressionLevel int 55 | 56 | const ( 57 | DefaultCompression CompressionLevel = 0 58 | NoCompression CompressionLevel = -1 59 | BestSpeed CompressionLevel = -2 60 | BestCompression CompressionLevel = -3 61 | 62 | // Positive CompressionLevel values are reserved to mean a numeric zlib 63 | // compression level, although that is not implemented yet. 64 | ) 65 | 66 | type opaquer interface { 67 | Opaque() bool 68 | } 69 | 70 | // Returns whether or not the image is fully opaque. 71 | func opaque(m image.Image) bool { 72 | if o, ok := m.(opaquer); ok { 73 | return o.Opaque() 74 | } 75 | b := m.Bounds() 76 | for y := b.Min.Y; y < b.Max.Y; y++ { 77 | for x := b.Min.X; x < b.Max.X; x++ { 78 | _, _, _, a := m.At(x, y).RGBA() 79 | if a != 0xffff { 80 | return false 81 | } 82 | } 83 | } 84 | return true 85 | } 86 | 87 | // The absolute value of a byte interpreted as a signed int8. 88 | func abs8(d uint8) int { 89 | if d < 128 { 90 | return int(d) 91 | } 92 | return 256 - int(d) 93 | } 94 | 95 | func (e *encoder) writeChunk(b []byte, name string) { 96 | if e.err != nil { 97 | return 98 | } 99 | n := uint32(len(b)) 100 | if int(n) != len(b) { 101 | e.err = UnsupportedError(name + " chunk is too large: " + strconv.Itoa(len(b))) 102 | return 103 | } 104 | binary.BigEndian.PutUint32(e.header[:4], n) 105 | e.header[4] = name[0] 106 | e.header[5] = name[1] 107 | e.header[6] = name[2] 108 | e.header[7] = name[3] 109 | crc := crc32.NewIEEE() 110 | crc.Write(e.header[4:8]) 111 | crc.Write(b) 112 | binary.BigEndian.PutUint32(e.footer[:4], crc.Sum32()) 113 | 114 | _, e.err = e.w.Write(e.header[:8]) 115 | if e.err != nil { 116 | return 117 | } 118 | _, e.err = e.w.Write(b) 119 | if e.err != nil { 120 | return 121 | } 122 | _, e.err = e.w.Write(e.footer[:4]) 123 | } 124 | 125 | func (e *encoder) writeIHDR() { 126 | b := e.m.Bounds() 127 | binary.BigEndian.PutUint32(e.tmp[0:4], uint32(b.Dx())) 128 | binary.BigEndian.PutUint32(e.tmp[4:8], uint32(b.Dy())) 129 | // Set bit depth and color type. 130 | switch e.cb { 131 | case cbG8: 132 | e.tmp[8] = 8 133 | e.tmp[9] = ctGrayscale 134 | case cbTC8: 135 | e.tmp[8] = 8 136 | e.tmp[9] = ctTrueColor 137 | case cbP8: 138 | e.tmp[8] = 8 139 | e.tmp[9] = ctPaletted 140 | case cbP4: 141 | e.tmp[8] = 4 142 | e.tmp[9] = ctPaletted 143 | case cbP2: 144 | e.tmp[8] = 2 145 | e.tmp[9] = ctPaletted 146 | case cbP1: 147 | e.tmp[8] = 1 148 | e.tmp[9] = ctPaletted 149 | case cbTCA8: 150 | e.tmp[8] = 8 151 | e.tmp[9] = ctTrueColorAlpha 152 | case cbG16: 153 | e.tmp[8] = 16 154 | e.tmp[9] = ctGrayscale 155 | case cbTC16: 156 | e.tmp[8] = 16 157 | e.tmp[9] = ctTrueColor 158 | case cbTCA16: 159 | e.tmp[8] = 16 160 | e.tmp[9] = ctTrueColorAlpha 161 | } 162 | e.tmp[10] = 0 // default compression method 163 | e.tmp[11] = 0 // default filter method 164 | e.tmp[12] = 0 // non-interlaced 165 | e.writeChunk(e.tmp[:13], "IHDR") 166 | } 167 | 168 | func (e *encoder) writePLTEAndTRNS(p color.Palette) { 169 | if len(p) < 1 || len(p) > 256 { 170 | e.err = FormatError("bad palette length: " + strconv.Itoa(len(p))) 171 | return 172 | } 173 | last := -1 174 | for i, c := range p { 175 | c1 := color.NRGBAModel.Convert(c).(color.NRGBA) 176 | e.tmp[3*i+0] = c1.R 177 | e.tmp[3*i+1] = c1.G 178 | e.tmp[3*i+2] = c1.B 179 | if c1.A != 0xff { 180 | last = i 181 | } 182 | e.tmp[3*256+i] = c1.A 183 | } 184 | e.writeChunk(e.tmp[:3*len(p)], "PLTE") 185 | if last != -1 { 186 | e.writeChunk(e.tmp[3*256:3*256+1+last], "tRNS") 187 | } 188 | } 189 | 190 | // An encoder is an io.Writer that satisfies writes by writing PNG IDAT chunks, 191 | // including an 8-byte header and 4-byte CRC checksum per Write call. Such calls 192 | // should be relatively infrequent, since writeIDATs uses a bufio.Writer. 193 | // 194 | // This method should only be called from writeIDATs (via writeImage). 195 | // No other code should treat an encoder as an io.Writer. 196 | func (e *encoder) Write(b []byte) (int, error) { 197 | e.writeChunk(b, "IDAT") 198 | if e.err != nil { 199 | return 0, e.err 200 | } 201 | return len(b), nil 202 | } 203 | 204 | // Chooses the filter to use for encoding the current row, and applies it. 205 | // The return value is the index of the filter and also of the row in cr that has had it applied. 206 | func filter(cr *[nFilter][]byte, pr []byte, bpp int) int { 207 | // We try all five filter types, and pick the one that minimizes the sum of absolute differences. 208 | // This is the same heuristic that libpng uses, although the filters are attempted in order of 209 | // estimated most likely to be minimal (ftUp, ftPaeth, ftNone, ftSub, ftAverage), rather than 210 | // in their enumeration order (ftNone, ftSub, ftUp, ftAverage, ftPaeth). 211 | cdat0 := cr[0][1:] 212 | cdat1 := cr[1][1:] 213 | cdat2 := cr[2][1:] 214 | cdat3 := cr[3][1:] 215 | cdat4 := cr[4][1:] 216 | pdat := pr[1:] 217 | n := len(cdat0) 218 | 219 | // The up filter. 220 | sum := 0 221 | for i := 0; i < n; i++ { 222 | cdat2[i] = cdat0[i] - pdat[i] 223 | sum += abs8(cdat2[i]) 224 | } 225 | best := sum 226 | filter := ftUp 227 | 228 | // The Paeth filter. 229 | sum = 0 230 | for i := 0; i < bpp; i++ { 231 | cdat4[i] = cdat0[i] - pdat[i] 232 | sum += abs8(cdat4[i]) 233 | } 234 | for i := bpp; i < n; i++ { 235 | cdat4[i] = cdat0[i] - paeth(cdat0[i-bpp], pdat[i], pdat[i-bpp]) 236 | sum += abs8(cdat4[i]) 237 | if sum >= best { 238 | break 239 | } 240 | } 241 | if sum < best { 242 | best = sum 243 | filter = ftPaeth 244 | } 245 | 246 | // The none filter. 247 | sum = 0 248 | for i := 0; i < n; i++ { 249 | sum += abs8(cdat0[i]) 250 | if sum >= best { 251 | break 252 | } 253 | } 254 | if sum < best { 255 | best = sum 256 | filter = ftNone 257 | } 258 | 259 | // The sub filter. 260 | sum = 0 261 | for i := 0; i < bpp; i++ { 262 | cdat1[i] = cdat0[i] 263 | sum += abs8(cdat1[i]) 264 | } 265 | for i := bpp; i < n; i++ { 266 | cdat1[i] = cdat0[i] - cdat0[i-bpp] 267 | sum += abs8(cdat1[i]) 268 | if sum >= best { 269 | break 270 | } 271 | } 272 | if sum < best { 273 | best = sum 274 | filter = ftSub 275 | } 276 | 277 | // The average filter. 278 | sum = 0 279 | for i := 0; i < bpp; i++ { 280 | cdat3[i] = cdat0[i] - pdat[i]/2 281 | sum += abs8(cdat3[i]) 282 | } 283 | for i := bpp; i < n; i++ { 284 | cdat3[i] = cdat0[i] - uint8((int(cdat0[i-bpp])+int(pdat[i]))/2) 285 | sum += abs8(cdat3[i]) 286 | if sum >= best { 287 | break 288 | } 289 | } 290 | if sum < best { 291 | best = sum 292 | filter = ftAverage 293 | } 294 | 295 | return filter 296 | } 297 | 298 | func zeroMemory(v []uint8) { 299 | for i := range v { 300 | v[i] = 0 301 | } 302 | } 303 | 304 | func (e *encoder) writeImage(w io.Writer, m image.Image, cb int, level int) error { 305 | if e.zw == nil || e.zwLevel != level { 306 | zw, err := zlib.NewWriterLevel(w, level) 307 | if err != nil { 308 | return err 309 | } 310 | e.zw = zw 311 | e.zwLevel = level 312 | } else { 313 | e.zw.Reset(w) 314 | } 315 | defer e.zw.Close() 316 | 317 | bitsPerPixel := 0 318 | 319 | switch cb { 320 | case cbG8: 321 | bitsPerPixel = 8 322 | case cbTC8: 323 | bitsPerPixel = 24 324 | case cbP8: 325 | bitsPerPixel = 8 326 | case cbP4: 327 | bitsPerPixel = 4 328 | case cbP2: 329 | bitsPerPixel = 2 330 | case cbP1: 331 | bitsPerPixel = 1 332 | case cbTCA8: 333 | bitsPerPixel = 32 334 | case cbTC16: 335 | bitsPerPixel = 48 336 | case cbTCA16: 337 | bitsPerPixel = 64 338 | case cbG16: 339 | bitsPerPixel = 16 340 | } 341 | 342 | // cr[*] and pr are the bytes for the current and previous row. 343 | // cr[0] is unfiltered (or equivalently, filtered with the ftNone filter). 344 | // cr[ft], for non-zero filter types ft, are buffers for transforming cr[0] under the 345 | // other PNG filter types. These buffers are allocated once and re-used for each row. 346 | // The +1 is for the per-row filter type, which is at cr[*][0]. 347 | b := m.Bounds() 348 | sz := 1 + (bitsPerPixel*b.Dx()+7)/8 349 | for i := range e.cr { 350 | if cap(e.cr[i]) < sz { 351 | e.cr[i] = make([]uint8, sz) 352 | } else { 353 | e.cr[i] = e.cr[i][:sz] 354 | } 355 | e.cr[i][0] = uint8(i) 356 | } 357 | cr := e.cr 358 | if cap(e.pr) < sz { 359 | e.pr = make([]uint8, sz) 360 | } else { 361 | e.pr = e.pr[:sz] 362 | zeroMemory(e.pr) 363 | } 364 | pr := e.pr 365 | 366 | gray, _ := m.(*image.Gray) 367 | gray16, _ := m.(*image.Gray16) 368 | rgba, _ := m.(*image.RGBA) 369 | paletted, _ := m.(*image.Paletted) 370 | nrgba, _ := m.(*image.NRGBA) 371 | 372 | for y := b.Min.Y; y < b.Max.Y; y++ { 373 | // Convert from colors to bytes. 374 | i := 1 375 | switch cb { 376 | case cbG8: 377 | if gray != nil { 378 | offset := (y - b.Min.Y) * gray.Stride 379 | copy(cr[0][1:], gray.Pix[offset:offset+b.Dx()]) 380 | } else { 381 | for x := b.Min.X; x < b.Max.X; x++ { 382 | c := color.GrayModel.Convert(m.At(x, y)).(color.Gray) 383 | cr[0][i] = c.Y 384 | i++ 385 | } 386 | } 387 | case cbTC8: 388 | // We have previously verified that the alpha value is fully opaque. 389 | cr0 := cr[0] 390 | stride, pix := 0, []byte(nil) 391 | if rgba != nil { 392 | stride, pix = rgba.Stride, rgba.Pix 393 | } else if nrgba != nil { 394 | stride, pix = nrgba.Stride, nrgba.Pix 395 | } 396 | if stride != 0 { 397 | j0 := (y - b.Min.Y) * stride 398 | j1 := j0 + b.Dx()*4 399 | for j := j0; j < j1; j += 4 { 400 | cr0[i+0] = pix[j+0] 401 | cr0[i+1] = pix[j+1] 402 | cr0[i+2] = pix[j+2] 403 | i += 3 404 | } 405 | } else { 406 | for x := b.Min.X; x < b.Max.X; x++ { 407 | r, g, b, _ := m.At(x, y).RGBA() 408 | cr0[i+0] = uint8(r >> 8) 409 | cr0[i+1] = uint8(g >> 8) 410 | cr0[i+2] = uint8(b >> 8) 411 | i += 3 412 | } 413 | } 414 | case cbP8: 415 | if paletted != nil { 416 | offset := (y - b.Min.Y) * paletted.Stride 417 | copy(cr[0][1:], paletted.Pix[offset:offset+b.Dx()]) 418 | } else { 419 | pi := m.(image.PalettedImage) 420 | for x := b.Min.X; x < b.Max.X; x++ { 421 | cr[0][i] = pi.ColorIndexAt(x, y) 422 | i += 1 423 | } 424 | } 425 | 426 | case cbP4, cbP2, cbP1: 427 | pi := m.(image.PalettedImage) 428 | 429 | var a uint8 430 | var c int 431 | for x := b.Min.X; x < b.Max.X; x++ { 432 | a = a<> 8) 472 | cr[0][i+1] = uint8(c.Y) 473 | i += 2 474 | } 475 | } 476 | case cbTC16: 477 | // We have previously verified that the alpha value is fully opaque. 478 | for x := b.Min.X; x < b.Max.X; x++ { 479 | r, g, b, _ := m.At(x, y).RGBA() 480 | cr[0][i+0] = uint8(r >> 8) 481 | cr[0][i+1] = uint8(r) 482 | cr[0][i+2] = uint8(g >> 8) 483 | cr[0][i+3] = uint8(g) 484 | cr[0][i+4] = uint8(b >> 8) 485 | cr[0][i+5] = uint8(b) 486 | i += 6 487 | } 488 | case cbTCA16: 489 | // Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied. 490 | for x := b.Min.X; x < b.Max.X; x++ { 491 | c := color.NRGBA64Model.Convert(m.At(x, y)).(color.NRGBA64) 492 | cr[0][i+0] = uint8(c.R >> 8) 493 | cr[0][i+1] = uint8(c.R) 494 | cr[0][i+2] = uint8(c.G >> 8) 495 | cr[0][i+3] = uint8(c.G) 496 | cr[0][i+4] = uint8(c.B >> 8) 497 | cr[0][i+5] = uint8(c.B) 498 | cr[0][i+6] = uint8(c.A >> 8) 499 | cr[0][i+7] = uint8(c.A) 500 | i += 8 501 | } 502 | } 503 | 504 | // Apply the filter. 505 | // Skip filter for NoCompression and paletted images (cbP8) as 506 | // "filters are rarely useful on palette images" and will result 507 | // in larger files (see http://www.libpng.org/pub/png/book/chapter09.html). 508 | f := ftNone 509 | if level != zlib.NoCompression && cb != cbP8 && cb != cbP4 && cb != cbP2 && cb != cbP1 { 510 | // Since we skip paletted images we don't have to worry about 511 | // bitsPerPixel not being a multiple of 8 512 | bpp := bitsPerPixel / 8 513 | f = filter(&cr, pr, bpp) 514 | } 515 | 516 | // Write the compressed bytes. 517 | if _, err := e.zw.Write(cr[f]); err != nil { 518 | return err 519 | } 520 | 521 | // The current row for y is the previous row for y+1. 522 | pr, cr[0] = cr[0], pr 523 | } 524 | return nil 525 | } 526 | 527 | // Write the actual image data to one or more IDAT chunks. 528 | func (e *encoder) writeIDATs() { 529 | if e.err != nil { 530 | return 531 | } 532 | if e.bw == nil { 533 | e.bw = bufio.NewWriterSize(e, 1<<15) 534 | } else { 535 | e.bw.Reset(e) 536 | } 537 | e.err = e.writeImage(e.bw, e.m, e.cb, levelToZlib(e.enc.CompressionLevel)) 538 | if e.err != nil { 539 | return 540 | } 541 | e.err = e.bw.Flush() 542 | } 543 | 544 | // This function is required because we want the zero value of 545 | // Encoder.CompressionLevel to map to zlib.DefaultCompression. 546 | func levelToZlib(l CompressionLevel) int { 547 | switch l { 548 | case DefaultCompression: 549 | return zlib.DefaultCompression 550 | case NoCompression: 551 | return zlib.NoCompression 552 | case BestSpeed: 553 | return zlib.BestSpeed 554 | case BestCompression: 555 | return zlib.BestCompression 556 | default: 557 | return zlib.DefaultCompression 558 | } 559 | } 560 | 561 | func (e *encoder) writeIEND() { e.writeChunk(nil, "IEND") } 562 | 563 | // Encode writes the Image m to w in PNG format. Any Image may be 564 | // encoded, but images that are not image.NRGBA might be encoded lossily. 565 | func Encode(w io.Writer, m image.Image) error { 566 | var e Encoder 567 | return e.Encode(w, m) 568 | } 569 | 570 | // Encode writes the Image m to w in PNG format. 571 | func (enc *Encoder) Encode(w io.Writer, m image.Image) error { 572 | // Obviously, negative widths and heights are invalid. Furthermore, the PNG 573 | // spec section 11.2.2 says that zero is invalid. Excessively large images are 574 | // also rejected. 575 | mw, mh := int64(m.Bounds().Dx()), int64(m.Bounds().Dy()) 576 | if mw <= 0 || mh <= 0 || mw >= 1<<32 || mh >= 1<<32 { 577 | return FormatError("invalid image size: " + strconv.FormatInt(mw, 10) + "x" + strconv.FormatInt(mh, 10)) 578 | } 579 | 580 | var e *encoder 581 | if enc.BufferPool != nil { 582 | buffer := enc.BufferPool.Get() 583 | e = (*encoder)(buffer) 584 | 585 | } 586 | if e == nil { 587 | e = &encoder{} 588 | } 589 | if enc.BufferPool != nil { 590 | defer enc.BufferPool.Put((*EncoderBuffer)(e)) 591 | } 592 | 593 | e.enc = enc 594 | e.w = w 595 | e.m = m 596 | 597 | var pal color.Palette 598 | // cbP8 encoding needs PalettedImage's ColorIndexAt method. 599 | if _, ok := m.(image.PalettedImage); ok { 600 | pal, _ = m.ColorModel().(color.Palette) 601 | } 602 | if pal != nil { 603 | if len(pal) <= 2 { 604 | e.cb = cbP1 605 | } else if len(pal) <= 4 { 606 | e.cb = cbP2 607 | } else if len(pal) <= 16 { 608 | e.cb = cbP4 609 | } else { 610 | e.cb = cbP8 611 | } 612 | } else { 613 | switch m.ColorModel() { 614 | case color.GrayModel: 615 | e.cb = cbG8 616 | case color.Gray16Model: 617 | e.cb = cbG16 618 | case color.RGBAModel, color.NRGBAModel, color.AlphaModel: 619 | if opaque(m) { 620 | e.cb = cbTC8 621 | } else { 622 | e.cb = cbTCA8 623 | } 624 | default: 625 | if opaque(m) { 626 | e.cb = cbTC16 627 | } else { 628 | e.cb = cbTCA16 629 | } 630 | } 631 | } 632 | 633 | _, e.err = io.WriteString(w, pngHeader) 634 | e.writeIHDR() 635 | if pal != nil { 636 | e.writePLTEAndTRNS(pal) 637 | } 638 | e.writeIDATs() 639 | e.writeIEND() 640 | return e.err 641 | } 642 | -------------------------------------------------------------------------------- /srvfb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Axel Wagner 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Command srvfb serves a framebuffer device over HTTP. 16 | package main 17 | 18 | import ( 19 | "encoding/binary" 20 | "errors" 21 | "flag" 22 | "fmt" 23 | "hash" 24 | "hash/fnv" 25 | "image" 26 | "io" 27 | "log" 28 | "mime" 29 | "mime/multipart" 30 | "net" 31 | "net/http" 32 | "net/textproto" 33 | "os" 34 | "strconv" 35 | "strings" 36 | "sync" 37 | "sync/atomic" 38 | "time" 39 | 40 | "github.com/Merovius/srvfb/internal/fb" 41 | "github.com/Merovius/srvfb/internal/png" 42 | 43 | "golang.org/x/sys/unix" 44 | ) 45 | 46 | func main() { 47 | if err := run(); err != nil { 48 | log.Println(err) 49 | os.Exit(1) 50 | } 51 | } 52 | 53 | func run() error { 54 | listen := flag.String("listen", "", "Address to listen on") 55 | proxy := flag.String("proxy", "", "Proxy the screen from the given address") 56 | device := flag.String("device", "", "Framebuffer device to serve") 57 | idle := flag.Duration("idle", 0, "Exit if there's no activity for this time. 0 disables this") 58 | flag.Parse() 59 | if flag.NArg() != 0 { 60 | return errors.New("usage: srvfb []") 61 | } 62 | 63 | if (*proxy == "") == (*device == "") { 64 | return errors.New("exactly one of -proxy or -device is required") 65 | } 66 | if len(listenFDs) > 1 { 67 | return errors.New("more than one file descriptor passed by service manager") 68 | } 69 | var ( 70 | l net.Listener 71 | err error 72 | ) 73 | if len(listenFDs) > 0 { 74 | if *listen != "" { 75 | return errors.New("can't use -listen with socket activation") 76 | } 77 | l, err = net.FileListener(listenFDs[0]) 78 | } else { 79 | if *listen == "" { 80 | return errors.New("no file descriptor passed by service manager and no -listen set") 81 | } 82 | l, err = net.Listen("tcp", *listen) 83 | } 84 | if err != nil { 85 | return err 86 | } 87 | l = wrapListener(l, *idle) 88 | 89 | h := new(handler) 90 | 91 | if *device != "" { 92 | h.fb, err = fb.Open(*device) 93 | } 94 | if err != nil { 95 | return err 96 | } 97 | h.proxy = *proxy 98 | http.Handle("/", h) 99 | if err = http.Serve(l, nil); err == errIdle { 100 | log.Printf("No activity for %v, shutting down", *idle) 101 | err = nil 102 | } 103 | return err 104 | } 105 | 106 | type handler struct { 107 | fb *fb.Device 108 | proxy string 109 | } 110 | 111 | func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 112 | log.Println(r.Method, r.URL.Path) 113 | if r.Method != "GET" { 114 | http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 115 | return 116 | } 117 | switch r.URL.Path { 118 | case "/": 119 | h.serveIndex(w, r) 120 | case "/video": 121 | h.serveVideo(w, r) 122 | case "/raw": 123 | if h.fb == nil { 124 | http.Error(w, "Not serving raw streams in proxy mode", http.StatusNotImplemented) 125 | return 126 | } 127 | h.serveRaw(w, r) 128 | case "/download": 129 | h.serveImage(w, r) 130 | default: 131 | http.Error(w, fmt.Sprintf("%q not found", r.URL.Path), http.StatusNotFound) 132 | } 133 | } 134 | 135 | const version = 1 136 | 137 | type rawHeader struct { 138 | Version uint8 139 | BitsPerPixel uint8 140 | Stride uint16 141 | Width uint32 142 | Height uint32 143 | } 144 | 145 | func (h *handler) serveRaw(w http.ResponseWriter, r *http.Request) { 146 | flusher, ok := w.(http.Flusher) 147 | if !ok { 148 | log.Println("Not a Flusher") 149 | http.Error(w, "Internal server error", http.StatusInternalServerError) 150 | return 151 | } 152 | 153 | im := new(image.Gray16) 154 | if err := h.readImage(im); err != nil { 155 | log.Println(err) 156 | http.Error(w, "Internal server error", http.StatusInternalServerError) 157 | return 158 | } 159 | 160 | w.Header().Set("Content-Type", "multipart/x-mixed-replace;boundary=endofsection") 161 | w.WriteHeader(http.StatusOK) 162 | 163 | mpw := multipart.NewWriter(w) 164 | mpw.SetBoundary("endofsection") 165 | hdr := make(textproto.MIMEHeader) 166 | hdr.Add("Content-Type", "binary/octet-stream") 167 | 168 | part, err := mpw.CreatePart(hdr) 169 | if err != nil { 170 | log.Println(err) 171 | return 172 | } 173 | rhdr := &rawHeader{version, 16, uint16(im.Stride), uint32(im.Rect.Dx()), uint32(im.Rect.Dy())} 174 | if err = binary.Write(part, binary.BigEndian, rhdr); err != nil { 175 | log.Println(err) 176 | return 177 | } 178 | part, err = mpw.CreatePart(hdr) 179 | if err != nil { 180 | log.Println(err) 181 | return 182 | } 183 | _, err = w.Write(im.Pix[im.Rect.Min.Y*im.Stride : im.Rect.Max.Y*im.Stride]) 184 | if err != nil { 185 | log.Println(err) 186 | return 187 | } 188 | flusher.Flush() 189 | 190 | var dedup deduper 191 | for { 192 | if err := h.readImage(im); err != nil { 193 | log.Println(err) 194 | return 195 | } 196 | pix := im.Pix[im.Rect.Min.Y*im.Stride : im.Rect.Max.Y*im.Stride] 197 | if dedup.skip(pix) { 198 | continue 199 | } 200 | w, err := mpw.CreatePart(hdr) 201 | if err != nil { 202 | log.Println(err) 203 | return 204 | } 205 | _, err = w.Write(pix) 206 | if err != nil { 207 | log.Println(err) 208 | return 209 | } 210 | flusher.Flush() 211 | } 212 | } 213 | 214 | func (h *handler) serveVideo(w http.ResponseWriter, r *http.Request) { 215 | flusher, ok := w.(http.Flusher) 216 | if !ok { 217 | log.Println("Not a flusher") 218 | http.Error(w, "Internal Server Error", 500) 219 | return 220 | } 221 | 222 | var reader interface { 223 | readImage(im *image.Gray16) error 224 | } 225 | 226 | if h.proxy != "" { 227 | c, err := dialProxy(h.proxy) 228 | if err != nil { 229 | log.Println(err) 230 | http.Error(w, "Bad Gateway", http.StatusBadGateway) 231 | return 232 | } 233 | defer c.close() 234 | reader = c 235 | } else { 236 | reader = h 237 | } 238 | 239 | w.Header().Set("Content-Type", "multipart/x-mixed-replace;boundary=endofsection") 240 | w.WriteHeader(http.StatusOK) 241 | 242 | mpw := multipart.NewWriter(w) 243 | mpw.SetBoundary("endofsection") 244 | hdr := make(textproto.MIMEHeader) 245 | hdr.Add("Content-Type", "image/png") 246 | im := new(image.Gray16) 247 | enc := &png.Encoder{CompressionLevel: png.BestSpeed} 248 | var dedup deduper 249 | for { 250 | if err := reader.readImage(im); err != nil { 251 | log.Println(err) 252 | return 253 | } 254 | if dedup.skip(im.Pix) { 255 | time.Sleep(500 * time.Millisecond) 256 | continue 257 | } 258 | w, err := mpw.CreatePart(hdr) 259 | if err != nil { 260 | log.Println(err) 261 | return 262 | } 263 | enc.Encode(w, im) 264 | flusher.Flush() 265 | } 266 | } 267 | 268 | func (h *handler) serveImage(w http.ResponseWriter, r *http.Request) { 269 | var reader interface { 270 | readImage(im *image.Gray16) error 271 | } 272 | 273 | if h.proxy != "" { 274 | c, err := dialProxy(h.proxy) 275 | if err != nil { 276 | log.Println(err) 277 | http.Error(w, "Bad Gateway", http.StatusBadGateway) 278 | return 279 | } 280 | defer c.close() 281 | reader = c 282 | } else { 283 | reader = h 284 | } 285 | 286 | im := new(image.Gray16) 287 | if err := reader.readImage(im); err != nil { 288 | log.Println(err) 289 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 290 | return 291 | } 292 | w.Header().Set("Content-Type", "image/png") 293 | png.Encode(w, im) 294 | } 295 | 296 | func (h *handler) serveIndex(w http.ResponseWriter, r *http.Request) { 297 | const idx = ` 298 | 299 | 300 | 301 | srvfb 302 | 320 | 321 | 360 | 361 | 362 |
363 | 364 | ` 365 | io.WriteString(w, idx) 366 | } 367 | 368 | func (h *handler) readImage(im *image.Gray16) error { 369 | vim, err := h.fb.Image() 370 | if err != nil { 371 | return err 372 | } 373 | gim, ok := vim.(*image.Gray16) 374 | if !ok { 375 | return errors.New("framebuffer is not 16-bit grayscale") 376 | } 377 | if len(im.Pix) < len(gim.Pix) { 378 | im.Pix = append(im.Pix, make([]byte, len(gim.Pix)-len(im.Pix))...) 379 | } 380 | copy(im.Pix, gim.Pix) 381 | for i := 1; i < len(im.Pix); i += 2 { 382 | im.Pix[i-1], im.Pix[i] = im.Pix[i], im.Pix[i-1] 383 | } 384 | im.Stride = gim.Stride 385 | im.Rect = gim.Rect 386 | return nil 387 | } 388 | 389 | type proxyconn struct { 390 | r *multipart.Reader 391 | closer io.Closer 392 | stride int 393 | width int 394 | height int 395 | } 396 | 397 | func dialProxy(addr string) (*proxyconn, error) { 398 | resp, err := http.Get(fmt.Sprintf("http://%s/raw", addr)) 399 | if err != nil { 400 | return nil, err 401 | } 402 | c := &proxyconn{closer: resp.Body} 403 | if err = c.readHdr(resp); err != nil { 404 | resp.Body.Close() 405 | return nil, err 406 | } 407 | return c, nil 408 | } 409 | 410 | func (c *proxyconn) readHdr(resp *http.Response) error { 411 | mt, parms, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) 412 | if err != nil { 413 | return err 414 | } 415 | if mt != "multipart/x-mixed-replace" { 416 | return fmt.Errorf("unknown media type %q", mt) 417 | } 418 | if parms["boundary"] == "" { 419 | return fmt.Errorf("no boundary in media type %q", resp.Header.Get("Content-Type")) 420 | } 421 | c.r = multipart.NewReader(resp.Body, parms["boundary"]) 422 | 423 | part, err := c.r.NextPart() 424 | if err != nil { 425 | return err 426 | } 427 | defer part.Close() 428 | if ct := part.Header.Get("Content-Type"); ct != "binary/octet-stream" { 429 | return fmt.Errorf("unknown Content-Type %q for part", ct) 430 | } 431 | 432 | var hdr rawHeader 433 | if err := binary.Read(part, binary.BigEndian, &hdr); err != nil { 434 | return err 435 | } 436 | log.Printf("Got header: %#x", hdr) 437 | if hdr.Version != version { 438 | return fmt.Errorf("incompatible version %d", hdr.BitsPerPixel) 439 | } 440 | if hdr.BitsPerPixel != 16 { 441 | return fmt.Errorf("incompatible bits per pixel %d", hdr.BitsPerPixel) 442 | } 443 | c.stride = int(hdr.Stride) 444 | c.width = int(hdr.Width) 445 | c.height = int(hdr.Height) 446 | return nil 447 | } 448 | 449 | func (c *proxyconn) readImage(im *image.Gray16) error { 450 | if len(im.Pix) != c.stride*c.height { 451 | *im = image.Gray16{ 452 | Pix: make([]byte, c.stride*c.height), 453 | Stride: c.stride, 454 | Rect: image.Rect(0, 0, c.width, c.height), 455 | } 456 | } 457 | part, err := c.r.NextPart() 458 | if err != nil { 459 | return err 460 | } 461 | defer part.Close() 462 | if ct := part.Header.Get("Content-Type"); ct != "binary/octet-stream" { 463 | return fmt.Errorf("unknown Content-Type %q for part", ct) 464 | } 465 | _, err = io.ReadFull(part, im.Pix) 466 | return err 467 | } 468 | 469 | func (c *proxyconn) close() { 470 | c.closer.Close() 471 | } 472 | 473 | // deduper keeps state to deduplicate sent frames. For some reason, Chrome only 474 | // seems to show a frame *after* the frame after has been sent (i.e. it lags 475 | // behind one frame), so we only start skipping after two consecutive frames 476 | // are identical. 477 | type deduper struct { 478 | h hash.Hash32 479 | h1 uint32 480 | h2 uint32 481 | } 482 | 483 | func (d *deduper) skip(b []byte) bool { 484 | if d.h == nil { 485 | d.h = fnv.New32a() 486 | } 487 | d.h.Reset() 488 | d.h.Write(b) 489 | h := d.h.Sum32() 490 | if h == d.h1 && h == d.h2 { 491 | return true 492 | } 493 | d.h1, d.h2 = d.h2, h 494 | return false 495 | } 496 | 497 | var errIdle = errors.New("idle timeout") 498 | 499 | // wrapListener wraps l with an idle timeout, if possible. A zero timeout 500 | // disables timeouts. If setting a timeout fails, the returned Listener falls 501 | // back to the behavior of the wrapped Listener. 502 | func wrapListener(l net.Listener, timeout time.Duration) net.Listener { 503 | tl, ok := l.(*net.TCPListener) 504 | if !ok || timeout == 0 { 505 | return l 506 | } 507 | return &listener{ 508 | TCPListener: tl, 509 | timeout: timeout, 510 | } 511 | } 512 | 513 | type listener struct { 514 | acceptMu sync.Mutex 515 | active uint32 516 | *net.TCPListener 517 | timeout time.Duration 518 | } 519 | 520 | // Accept implements net.Conn. Connections returned by Accept are tracked. Once 521 | // all active connections are closed and the idle timeout expires, Accept 522 | // returns errIdle. 523 | func (l *listener) Accept() (net.Conn, error) { 524 | l.acceptMu.Lock() 525 | defer l.acceptMu.Unlock() 526 | 527 | if atomic.LoadUint32(&l.active) == 0 { 528 | l.SetDeadline(time.Now().Add(l.timeout)) 529 | } 530 | c, err := l.TCPListener.Accept() 531 | if err == nil { 532 | atomic.AddUint32(&l.active, 1) 533 | l.SetDeadline(time.Time{}) 534 | return &conn{l: l, Conn: c}, nil 535 | } 536 | to, ok := err.(interface { 537 | Timeout() bool 538 | }) 539 | if !ok || !to.Timeout() { 540 | return nil, err 541 | } 542 | return nil, errIdle 543 | } 544 | 545 | type conn struct { 546 | o sync.Once 547 | l *listener 548 | net.Conn 549 | } 550 | 551 | func (c *conn) Close() error { 552 | c.o.Do(func() { 553 | if atomic.AddUint32(&c.l.active, ^uint32(0)) == 0 { 554 | c.l.SetDeadline(time.Now().Add(c.l.timeout)) 555 | } 556 | }) 557 | return c.Conn.Close() 558 | } 559 | 560 | var listenFDs []*os.File 561 | 562 | func init() { 563 | defer func() { 564 | os.Unsetenv("LISTEN_PID") 565 | os.Unsetenv("LISTEN_FDS") 566 | os.Unsetenv("LISTEN_FDNAMES") 567 | }() 568 | var ( 569 | pid int 570 | fds int 571 | names []string 572 | err error 573 | ) 574 | if s := os.Getenv("LISTEN_PID"); s == "" { 575 | return 576 | } else { 577 | pid, err = strconv.Atoi(s) 578 | } 579 | if err != nil { 580 | log.Printf("Can't parse $LISTEN_PID: %v", err) 581 | return 582 | } 583 | if os.Getpid() != pid { 584 | return 585 | } 586 | if s := os.Getenv("LISTEN_FDS"); s == "" { 587 | return 588 | } else { 589 | fds, err = strconv.Atoi(s) 590 | } 591 | if err != nil { 592 | log.Printf("Can't parse $LISTEN_PID: %v", err) 593 | return 594 | } 595 | if s := os.Getenv("LISTEN_FDNAMES"); s != "" { 596 | names = strings.Split(s, ":") 597 | } 598 | for i := len(names); i < fds; i++ { 599 | names = append(names, "unknown") 600 | } 601 | for i := 0; i < fds; i++ { 602 | unix.FcntlInt(3+uintptr(i), unix.F_SETFD, unix.FD_CLOEXEC) 603 | listenFDs = append(listenFDs, os.NewFile(3+uintptr(i), names[i])) 604 | } 605 | } 606 | --------------------------------------------------------------------------------