├── LICENSE ├── Makefile ├── README.md ├── colors.h ├── getprogname.c ├── sprite.1 ├── sprite.c ├── sprite.h └── strtonum.c /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-2021 Brian Callahan 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # sprite Makefile 2 | # Written by Brian Callahan 3 | # and released into the Public Domain 4 | 5 | CC ?= cc 6 | CFLAGS ?= -O2 -pipe 7 | CFLAGS += -I/usr/local/include 8 | CFLAGS += -DHAVE_GETPROGNAME -DHAVE_STRTONUM 9 | 10 | PREFIX ?= /usr/local 11 | BINDIR ?= ${PREFIX}/bin 12 | MANDIR ?= ${PREFIX}/man/man1 13 | 14 | PROG = sprite 15 | OBJS = getprogname.o sprite.o strtonum.o 16 | 17 | all: ${OBJS} 18 | ${CC} ${LDFLAGS} -o ${PROG} ${OBJS} -lcurses -L/usr/local/lib -lpng 19 | 20 | install: 21 | install -d -m 755 ${BINDIR} 22 | install -d -m 755 ${MANDIR} 23 | install -c -s -m 555 ${PROG} ${BINDIR} 24 | install -c -m 444 ${PROG}.1 ${MANDIR} 25 | 26 | clean: 27 | rm -f ${PROG} ${OBJS} ${PROG}.core 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sprite 2 | ====== 3 | Sprite is an ncurses-based sprite editor. 4 | You can use it to create and share pixel art. 5 | 6 | By default, sprite sets up a 16x16 pixel canvas. 7 | If you'd like a larger 32x32 pixel canvas, start sprite with the -e 8 | flag. 9 | If your terminal is not large enough to fit a 32x32 canvas, it will 10 | use a 16x16 canvas instead. 11 | If you'd like a smaller 8x8 pixel canvas, start sprite with the -s 12 | flag. 13 | 14 | Most terminals do not have 1:1 square character cells. 15 | This may cause the canvas to have a different aspect ratio than 16 | expectations. 17 | PNG export does create standard 1:1 square pixels; exported images 18 | will look correct. 19 | 20 | Save file format 21 | ---------------- 22 | Images are saved as a flat text file, one pixel per line, in the form 23 | ``` 24 | y,x,color 25 | ``` 26 | Color is an index number from 0-255 that matches XTerm color. 27 | Only pixels with color are saved; transparent pixels are left out. 28 | The .spr and .txt file extensions are used by convention but any file 29 | extension can be used for save files. 30 | 31 | Images can be exported to PNG, but sprite is not able to open PNGs, 32 | so please save images rather than export if you want to work on them 33 | over multiple sessions. The .png file extension is recommended for 34 | export; sprite does not add the file extension automatically when 35 | exporting. 36 | 37 | Requirements 38 | ------------ 39 | You need ncurses with 256 color support. 40 | While it will work with fewer than 256 colors it will not be as good. 41 | 42 | You also need libpng for PNG export support. 43 | 44 | Building 45 | -------- 46 | Just run `make`. 47 | 48 | Remove `-DHAVE_STRTONUM` if your system needs the strtonum(3) function. 49 | 50 | Remove `-DHAVE_GETPROGNAME` if your system needs the getprogname(3) 51 | function. 52 | 53 | License 54 | ------- 55 | ISC License. 56 | See `LICENSE` for details. 57 | -------------------------------------------------------------------------------- /colors.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2021 Brian Callahan 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | /* 18 | * From https://jonasjacek.github.io/colors/ 19 | */ 20 | 21 | static const int colors[256] = { 22 | 0x00000000, /* Black 0 */ 23 | 0x00800000, /* Maroon 1 */ 24 | 0x00008000, /* Green 2 */ 25 | 0x00808000, /* Olive 3 */ 26 | 0x00000080, /* Navy 4 */ 27 | 0x00800080, /* Purple 5 */ 28 | 0x00008080, /* Teal 6 */ 29 | 0x00c0c0c0, /* Silver 7 (Transparent 1) */ 30 | 0x00808080, /* Grey 8 (Transparent 2) */ 31 | 0x00ff0000, /* Red 9 */ 32 | 0x0000ff00, /* Lime 10 */ 33 | 0x00ffff00, /* Yellow 11 */ 34 | 0x000000ff, /* Blue 12 */ 35 | 0x00ff00ff, /* Fuchsia 13 */ 36 | 0x0000ffff, /* Aqua 14 */ 37 | 0x00ffffff, /* White 15 */ 38 | 0x00000000, /* Grey0 16 (Same as Black) */ 39 | 0x0000005f, /* NavyBlue 17 */ 40 | 0x00000087, /* DarkBlue 18 */ 41 | 0x000000af, /* Blue3 19 */ 42 | 0x000000d7, /* Blue3 20 */ 43 | 0x000000ff, /* Blue1 21 (Same as Blue) */ 44 | 0x00005f00, /* DarkGreen 22 */ 45 | 0x00005f5f, /* DeepSkyBlue4 23 */ 46 | 0x00005f87, /* DeepSkyBlue4 24 */ 47 | 0x00005faf, /* DeepSkyBlue4 25 */ 48 | 0x00005fd7, /* DodgerBlue3 26 */ 49 | 0x00005fff, /* DodgerBlue2 27 */ 50 | 0x00008700, /* Green4 28 */ 51 | 0x0000875f, /* SpringGreen4 29 */ 52 | 0x00008787, /* Turquoise4 30 */ 53 | 0x000087af, /* DeepSkyBlue3 31 */ 54 | 0x000087d7, /* DeepSkyBlue3 32 */ 55 | 0x000087ff, /* DodgerBlue1 33 */ 56 | 0x0000af00, /* Green3 34 */ 57 | 0x0000af5f, /* SpringGreen3 35 */ 58 | 0x0000af87, /* DarkCyan 36 */ 59 | 0x0000afaf, /* LightSeaGreen 37 */ 60 | 0x0000afd7, /* DeepSkyBlue2 38 */ 61 | 0x0000afff, /* DeepSkyBlue1 39 */ 62 | 0x0000d700, /* Green3 40 */ 63 | 0x0000d75f, /* SpringGreen3 41 */ 64 | 0x0000d787, /* SpringGreen2 42 */ 65 | 0x0000d7af, /* Cyan3 43 */ 66 | 0x0000d7d7, /* DarkTurquoise 44 */ 67 | 0x0000d7ff, /* Turquoise2 45 */ 68 | 0x0000ff00, /* Green1 46 (Same as Lime) */ 69 | 0x0000ff5f, /* SpringGreen2 47 */ 70 | 0x0000ff87, /* SpringGreen1 48 */ 71 | 0x0000ffaf, /* MediumSpringGreen 49 */ 72 | 0x0000ffd7, /* Cyan2 50 */ 73 | 0x0000ffff, /* Cyan1 51 (Same as Aqua) */ 74 | 0x005f0000, /* DarkRed 52 */ 75 | 0x005f005f, /* DeepPink4 53 */ 76 | 0x005f0087, /* Purple4 54 */ 77 | 0x005f00af, /* Purple4 55 */ 78 | 0x005f00d7, /* Purple3 56 */ 79 | 0x005f00ff, /* BlueViolet 57 */ 80 | 0x005f5f00, /* Orange4 58 */ 81 | 0x005f5f5f, /* Grey37 59 */ 82 | 0x005f5f87, /* MediumPurple4 60 */ 83 | 0x005f5faf, /* SlateBlue3 61 */ 84 | 0x005f5fd7, /* SlateBlue3 62 */ 85 | 0x005f5fff, /* RoyalBlue1 63 */ 86 | 0x005f8700, /* Chartreuse4 64 */ 87 | 0x005f875f, /* DarkSeaGreen4 65 */ 88 | 0x005f8787, /* PaleTurquoise4 66 */ 89 | 0x005f87af, /* SteelBlue 67 */ 90 | 0x005f87d7, /* SteelBlue3 68 */ 91 | 0x005f87ff, /* CornflowerBlue 69 */ 92 | 0x005faf00, /* Chartreuse3 70 */ 93 | 0x005faf5f, /* DarkSeaGreen4 71 */ 94 | 0x005faf87, /* CadetBlue 72 */ 95 | 0x005fafaf, /* CadetBlue 73 */ 96 | 0x005fafd7, /* SkyBlue3 74 */ 97 | 0x005fafff, /* SteelBlue1 75 */ 98 | 0x005fd700, /* Chartreuse3 76 */ 99 | 0x005fd75f, /* PaleGreen3 77 */ 100 | 0x005fd787, /* SeaGreen3 78 */ 101 | 0x005fd7af, /* Aquamarine3 79 */ 102 | 0x005fd7d7, /* MediumTurquoise 80 */ 103 | 0x005fd7ff, /* SteelBlue1 81 */ 104 | 0x005fff00, /* Chartreuse2 82 */ 105 | 0x005fff5f, /* SeaGreen2 83 */ 106 | 0x005fff87, /* SeaGreen1 84 */ 107 | 0x005fffaf, /* SeaGreen1 85 */ 108 | 0x005fffd7, /* Aquamarine1 86 */ 109 | 0x005fffff, /* DarkSlateGray2 87 */ 110 | 0x00870000, /* DarkRed 88 */ 111 | 0x0087005f, /* DeepPink4 89 */ 112 | 0x00870087, /* DarkMagenta 90 */ 113 | 0x008700af, /* DarkMagenta 91 */ 114 | 0x008700d7, /* DarkViolet 92 */ 115 | 0x008700ff, /* Purple 93 */ 116 | 0x00875f00, /* Orange4 94 */ 117 | 0x00875f5f, /* LightPink4 95 */ 118 | 0x00875f87, /* Plum4 96 */ 119 | 0x00875faf, /* MediumPurple3 97 */ 120 | 0x00875fd7, /* MediumPurple3 98 */ 121 | 0x00875fff, /* SlateBlue1 99 */ 122 | 0x00878700, /* Yellow4 100 */ 123 | 0x0087875f, /* Wheat4 101 */ 124 | 0x00878787, /* Grey53 102 */ 125 | 0x008787af, /* LightSlateGrey 103 */ 126 | 0x008787d7, /* MediumPurple 104 */ 127 | 0x008787ff, /* LightSlateBlue 105 */ 128 | 0x0087af00, /* Yellow4 106 */ 129 | 0x0087af5f, /* DarkOliveGreen3 107 */ 130 | 0x0087af87, /* DarkSeaGreen 108 */ 131 | 0x0087afaf, /* LightSkyBlue3 109 */ 132 | 0x0087afd7, /* LightSkyBlue3 110 */ 133 | 0x0087afff, /* SkyBlue2 111 */ 134 | 0x0087d700, /* Chartreuse2 112 */ 135 | 0x0087d75f, /* DarkOliveGree3 113 */ 136 | 0x0087d787, /* PaleGreen3 114 */ 137 | 0x0087d7af, /* DarkSeaGreen3 115 */ 138 | 0x0087d7d7, /* DarkSlateGray3 116 */ 139 | 0x0087d7ff, /* SkyBlue1 117 */ 140 | 0x0087ff00, /* Chartreuse1 118 */ 141 | 0x0087ff5f, /* LightGreen 119 */ 142 | 0x0087ff87, /* LightGreen 120 */ 143 | 0x0087ffaf, /* PaleGreen1 121 */ 144 | 0x0087ffd7, /* Aquamarine1 122 */ 145 | 0x0087ffff, /* DarkSlateGray1 123 */ 146 | 0x00af0000, /* Red3 124 */ 147 | 0x00af005f, /* DeepPink4 125 */ 148 | 0x00af0087, /* MediumVioletRed 126 */ 149 | 0x00af00af, /* Magenta3 127 */ 150 | 0x00af00d7, /* DarkViolet 128 */ 151 | 0x00af00ff, /* Purple 129 */ 152 | 0x00af5f00, /* DarkOrange3 130 */ 153 | 0x00af5f5f, /* IndianRed 131 */ 154 | 0x00af5f87, /* HotPink3 132 */ 155 | 0x00af5faf, /* MediumOrchid3 133 */ 156 | 0x00af5fd7, /* MediumOrchid 134 */ 157 | 0x00af5fff, /* MediumPurple2 135 */ 158 | 0x00af8700, /* DarkGoldenrod 136 */ 159 | 0x00af875f, /* LightSalmon3 137 */ 160 | 0x00af8787, /* RosyBrown 138 */ 161 | 0x00af87af, /* Grey63 139 */ 162 | 0x00af87d7, /* MediumPurple2 140 */ 163 | 0x00af87ff, /* MediumPurple1 141 */ 164 | 0x00afaf00, /* Gold3 142 */ 165 | 0x00afaf5f, /* DarkKhaki 143 */ 166 | 0x00afaf87, /* NavajoWhite3 144 */ 167 | 0x00afafaf, /* Grey69 145 */ 168 | 0x00afafd7, /* LightSteelBlue3 146 */ 169 | 0x00afafff, /* LightSteelBlue 147 */ 170 | 0x00afd700, /* Yellow3 148 */ 171 | 0x00afd75f, /* DarkOliveGreen3 149 */ 172 | 0x00afd787, /* DarkSeaGreen3 150 */ 173 | 0x00afd7af, /* DarkSeaGreen2 151 */ 174 | 0x00afd7d7, /* LightCyan3 152 */ 175 | 0x00afd7ff, /* LightSkyBlue1 153 */ 176 | 0x00afff00, /* GreenYellow 154 */ 177 | 0x00afff5f, /* DarkOliveGreen2 155 */ 178 | 0x00afff87, /* PaleGreen1 156 */ 179 | 0x00afffaf, /* DarkSeaGreen2 157 */ 180 | 0x00afffd7, /* DarkSeaGreen1 158 */ 181 | 0x00afffff, /* PaleTurquoise1 159 */ 182 | 0x00d70000, /* Red3 160 */ 183 | 0x00d7005f, /* DeepPink3 161 */ 184 | 0x00d70087, /* DeepPink3 162 */ 185 | 0x00d700af, /* Magenta3 163 */ 186 | 0x00d700d7, /* Magenta3 164 */ 187 | 0x00d700ff, /* Magenta2 165 */ 188 | 0x00d75f00, /* DarkOrange3 166 */ 189 | 0x00d75f5f, /* IndianRed 167 */ 190 | 0x00d75f87, /* HotPink3 168 */ 191 | 0x00d75faf, /* HotPink2 169 */ 192 | 0x00d75fd7, /* Orchid 170 */ 193 | 0x00d75fff, /* MediumOrchid1 171 */ 194 | 0x00d78700, /* Orange3 172 */ 195 | 0x00d7875f, /* LightSalmon3 173 */ 196 | 0x00d78787, /* LightPink3 174 */ 197 | 0x00d787af, /* Pink3 175 */ 198 | 0x00d787d7, /* Plum3 176 */ 199 | 0x00d787ff, /* Violet 177 */ 200 | 0x00d7af00, /* Gold3 178 */ 201 | 0x00d7af5f, /* LightGoldenrod3 179 */ 202 | 0x00d7af87, /* Tan 180 */ 203 | 0x00d7afaf, /* MistyRose3 181 */ 204 | 0x00d7afd7, /* Thistle3 182 */ 205 | 0x00d7afff, /* Plum2 183 */ 206 | 0x00d7d700, /* Yellow3 184 */ 207 | 0x00d7d75f, /* Khaki3 185 */ 208 | 0x00d7d787, /* LightGoldenrod2 186 */ 209 | 0x00d7d7af, /* LightYellow3 187 */ 210 | 0x00d7d7d7, /* Grey84 188 */ 211 | 0x00d7d7ff, /* LightSteelBlue1 189 */ 212 | 0x00d7ff00, /* Yellow2 190 */ 213 | 0x00d7ff5f, /* DarkOliveGreen1 191 */ 214 | 0x00d7ff87, /* DarkOliveGreen1 192 */ 215 | 0x00d7ffaf, /* DarkSeaGreen1 193 */ 216 | 0x00d7ffd7, /* Honeydew2 194 */ 217 | 0x00d7ffff, /* LightCyan1 195 */ 218 | 0x00ff0000, /* Red1 196 */ 219 | 0x00ff005f, /* DeepPink2 197 */ 220 | 0x00ff0087, /* DeepPink1 198 */ 221 | 0x00ff00af, /* DeepPink1 199 */ 222 | 0x00ff00d7, /* Magenta2 200 */ 223 | 0x00ff00ff, /* Magenta1 201 */ 224 | 0x00ff5f00, /* OrangeRed1 202 */ 225 | 0x00ff5f5f, /* IndianRed1 203 */ 226 | 0x00ff5f87, /* IndianRed1 204 */ 227 | 0x00ff5faf, /* HotPink 205 */ 228 | 0x00ff5fd7, /* HotPink 206 */ 229 | 0x00ff5fff, /* MediumOrchid1 207 */ 230 | 0x00ff8700, /* DarkOrange 208 */ 231 | 0x00ff875f, /* Salmon1 209 */ 232 | 0x00ff8787, /* LightCoral 210 */ 233 | 0x00ff87af, /* PaleVioletRed1 211 */ 234 | 0x00ff87d7, /* Orchid2 212 */ 235 | 0x00ff87ff, /* Orchid1 213 */ 236 | 0x00ffaf00, /* Orange1 214 */ 237 | 0x00ffaf5f, /* SandyBrown 215 */ 238 | 0x00ffaf87, /* LightSalmon1 216 */ 239 | 0x00ffafaf, /* LightPink1 217 */ 240 | 0x00ffafd7, /* Pink1 218 */ 241 | 0x00ffafff, /* Plum1 219 */ 242 | 0x00ffd700, /* Gold1 220 */ 243 | 0x00ddf75f, /* LightGoldenrod2 221 */ 244 | 0x00ffd787, /* LightGoldenrod2 222 */ 245 | 0x00ffd7af, /* NavajoWhite1 223 */ 246 | 0x00ffd7d7, /* MistyRose1 224 */ 247 | 0x00ffd7ff, /* Thistle1 225 */ 248 | 0x00ffff00, /* Yellow1 226 */ 249 | 0x00ffff5f, /* LightGoldenrod1 227 */ 250 | 0x00ffff87, /* Khaki1 228 */ 251 | 0x00ffffaf, /* Wheat1 229 */ 252 | 0x00ffffd7, /* Cornsilk1 230 */ 253 | 0x00ffffff, /* Grey100 231 */ 254 | 0x00080808, /* Grey3 232 */ 255 | 0x00121212, /* Grey7 233 */ 256 | 0x001c1c1c, /* Grey11 234 */ 257 | 0x00262626, /* Grey15 235 */ 258 | 0x00303030, /* Grey19 236 */ 259 | 0x003a3a3a, /* Grey23 237 */ 260 | 0x00444444, /* Grey27 238 */ 261 | 0x004e4e4e, /* Grey30 239 */ 262 | 0x00585858, /* Grey35 240 */ 263 | 0x00626262, /* Grey39 241 */ 264 | 0x006c6c6c, /* Grey42 242 */ 265 | 0x00767676, /* Grey46 243 */ 266 | 0x00808080, /* Grey50 244 */ 267 | 0x008a8a8a, /* Grey54 245 */ 268 | 0x00949494, /* Grey58 246 */ 269 | 0x009e9e9e, /* Grey62 247 */ 270 | 0x00a8a8a8, /* Grey66 248 */ 271 | 0x00b2b2b2, /* Grey70 249 */ 272 | 0x00bcbcbc, /* Grey74 250 */ 273 | 0x00c6c6c6, /* Grey78 251 */ 274 | 0x00d0d0d0, /* Grey82 252 */ 275 | 0x00dadada, /* Grey85 253 */ 276 | 0x00e4e4e4, /* Grey89 254 */ 277 | 0x00eeeeee /* Grey93 255 */ 278 | }; 279 | -------------------------------------------------------------------------------- /getprogname.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2021 Brian Callahan 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef HAVE_GETPROGNAME 18 | 19 | const char * 20 | getprogname(void) 21 | { 22 | 23 | return "sprite"; 24 | } 25 | 26 | #endif /* !HAVE_GETPROGNAME */ 27 | -------------------------------------------------------------------------------- /sprite.1: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" sprite - ncurses-based sprite editor 3 | .\" 4 | .\" Copyright (c) 2020-2021 Brian Callahan 5 | .\" 6 | .\" Permission to use, copy, modify, and distribute this software for any 7 | .\" purpose with or without fee is hereby granted, provided that the above 8 | .\" copyright notice and this permission notice appear in all copies. 9 | .\" 10 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | .\" 18 | .Dd January 15, 2021 19 | .Dt SPRITE 1 20 | .Os 21 | .Sh NAME 22 | .Nm sprite 23 | .Nd ncurses-based sprite editor 24 | .Sh SYNOPSIS 25 | .Nm 26 | .Op Fl es 27 | .Op Ar file 28 | .Sh DESCRIPTION 29 | .Nm 30 | is an ncurses-based sprite editor. 31 | You can use it to create and share pixel art. 32 | .Pp 33 | The options are as follows: 34 | .Bl -tag -width Ds 35 | .It Fl e 36 | Use an extended canvas of 32x32 pixels instead of the default 16x16 pixels, 37 | if the terminal is large enough to support the larger canvas. 38 | .It Fl s 39 | Use a smaller canvas of 8x8 pixels instead of the default 16x16 pixels. 40 | .El 41 | .Sh AUTHORS 42 | .Nm 43 | was written by 44 | .An Brian Callahan Aq Mt bcallah@openbsd.org . 45 | .Sh CAVEATS 46 | Console aspect ratio may not match user expectations. 47 | PNG export creates standard square pixels. 48 | .Pp 49 | Supports the 256 terminal colors only. 50 | -------------------------------------------------------------------------------- /sprite.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2021 Brian Callahan 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | /* 18 | * sprite -- console sprite editor 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "colors.h" 31 | #include "sprite.h" 32 | 33 | static char extended, small; 34 | 35 | /* 36 | * color -1 == transparent 37 | */ 38 | typedef struct pixel { 39 | char x; 40 | char y; 41 | int color; 42 | } pixel_t; 43 | static pixel_t pixel[32][32]; 44 | 45 | static int 46 | switch_color(int color) 47 | { 48 | 49 | if (color == 7) 50 | return 8; 51 | 52 | return 7; 53 | } 54 | 55 | static void 56 | draw_transparency(void) 57 | { 58 | int i, j, k = 7; 59 | 60 | for (i = 4; i < 20 + (extended ? 16 : (small ? -8 : 0)); i++) { 61 | if ((i - 4) % (extended ? 8 : (small ? 2 : 4)) == 0) 62 | k = switch_color(k); 63 | for (j = 32; j < 48 + (extended ? 16 : (small ? -8 : 0)); j++) { 64 | if (j % (extended ? 8: (small ? 2 : 4)) == 0) 65 | k = switch_color(k); 66 | attron(COLOR_PAIR(k)); 67 | mvaddch(i, j, ' '); 68 | attroff(COLOR_PAIR(k)); 69 | } 70 | } 71 | } 72 | 73 | static void 74 | draw_screen(int y, int x, int color) 75 | { 76 | int i, j; 77 | 78 | draw_transparency(); 79 | 80 | for (i = 4; i < 20 + (extended ? 16 : (small ? -8 : 0)); i++) { 81 | for (j = 32; j < 48 + (extended ? 16 : (small ? -8 : 0)); j++) { 82 | if (pixel[i - 4][j - 32].color != -1) { 83 | attron(COLOR_PAIR(pixel[i - 4][j - 32].color)); 84 | mvaddch(i, j, ' '); 85 | attroff(COLOR_PAIR(pixel[i - 4][j - 32].color)); 86 | } 87 | } 88 | } 89 | 90 | attron(COLOR_PAIR(color)); 91 | mvaddch(y, x, ' '); 92 | attroff(COLOR_PAIR(color)); 93 | 94 | refresh(); 95 | } 96 | 97 | static void 98 | init_pixels(void) 99 | { 100 | int i, j; 101 | 102 | for (i = 0; i < 16 + (extended ? 16 : (small ? -8 : 0)); i++) { 103 | for (j = 0; j < 16 + (extended ? 16 : (small ? -8 : 0)); j++) { 104 | pixel[i][j].x = j; 105 | pixel[i][j].y = i; 106 | pixel[i][j].color = -1; 107 | } 108 | } 109 | } 110 | 111 | static void 112 | init_colors(void) 113 | { 114 | int i; 115 | 116 | for (i = 0; i < 256; i++) 117 | init_pair(i, i, i); 118 | } 119 | 120 | static void 121 | instructions(void) 122 | { 123 | int i; 124 | 125 | move(2, 35 + (extended ? 8 : 0)); 126 | printw("Sprite 1.8"); 127 | 128 | move(4, 50 + (extended ? 16 : 0)); 129 | printw("Key commands"); 130 | move(5, 50 + (extended ? 16 : 0)); 131 | printw("============"); 132 | move(6, 50 + (extended ? 16 : 0)); 133 | printw("Arrow keys: move"); 134 | move(7, 50 + (extended ? 16 : 0)); 135 | printw("Spacebar: draw pixel"); 136 | move(8, 50 + (extended ? 16 : 0)); 137 | printw("/: toggle spacebar lock"); 138 | move(9, 50 + (extended ? 16 : 0)); 139 | printw("c: change color"); 140 | move(10, 50 + (extended ? 16 : 0)); 141 | printw("d: delete pixel"); 142 | move(11, 50 + (extended ? 16 : 0)); 143 | printw("f: fill region"); 144 | move(12, 50 + (extended ? 16 : 0)); 145 | printw("u: undo"); 146 | move(14, 50 + (extended ? 16 : 0)); 147 | printw("e: export to PNG"); 148 | move(15, 50 + (extended ? 16 : 0)); 149 | printw("s: save"); 150 | move(16, 50 + (extended ? 16 : 0)); 151 | printw("q: quit"); 152 | } 153 | 154 | static void 155 | color_panel(void) 156 | { 157 | int i, j, k = 0; 158 | 159 | move(4, 14); 160 | for (i = 4; i < 20; i++) { 161 | for (j = 14; j < 30; j++) { 162 | attron(COLOR_PAIR(k)); 163 | mvaddch(i, j, ' '); 164 | attroff(COLOR_PAIR(k++)); 165 | } 166 | } 167 | } 168 | 169 | static void 170 | scrinit(void) 171 | { 172 | int i, x, y; 173 | 174 | if (has_colors() == FALSE) { 175 | endwin(); 176 | errx(1, "sprite requires color support"); 177 | } 178 | 179 | start_color(); 180 | init_colors(); 181 | 182 | getmaxyx(stdscr, y, x); 183 | if (y < 39 || x < 90) 184 | extended = 0; 185 | if (y < 23 || x < 74) { 186 | endwin(); 187 | errx(1, "terminal too small!"); 188 | } 189 | 190 | move(3, 31); 191 | 192 | addch(ACS_ULCORNER); 193 | for (i = 0; i < 16 + (extended ? 16 : (small ? -8 : 0)); i++) 194 | addch(ACS_HLINE); 195 | addch(ACS_URCORNER); 196 | 197 | for (i = 4; i < 20 + (extended ? 16 : (small ? -8 : 0)); i++) { 198 | move(i, 31); 199 | addch(ACS_VLINE); 200 | move(i, 48 + (extended ? 16 : (small ? -8 : 0))); 201 | addch(ACS_VLINE); 202 | } 203 | 204 | move(i, 31); 205 | 206 | addch(ACS_LLCORNER); 207 | for (i = 0; i < 16 + (extended ? 16 : (small ? -8 : 0)); i++) 208 | addch(ACS_HLINE); 209 | addch(ACS_LRCORNER); 210 | } 211 | 212 | static int 213 | change_color(int y, int x, int color) 214 | { 215 | const char *errstr; 216 | char buf[4]; 217 | int i, new_color; 218 | 219 | memset(buf, 0, sizeof(buf)); 220 | 221 | move(21 + (extended ? 16 : (small ? -8 : 0)), 31); 222 | printw("Color [0-255]: "); 223 | echo(); 224 | getnstr(buf, sizeof(buf) - 1); 225 | noecho(); 226 | 227 | clear(); 228 | scrinit(); 229 | color_panel(); 230 | instructions(); 231 | 232 | move(y, x); 233 | 234 | new_color = strtonum(buf, 0, 255, &errstr); 235 | if (errstr != NULL) 236 | return color; 237 | 238 | return new_color; 239 | } 240 | 241 | static void 242 | do_undo(int go) 243 | { 244 | static pixel_t temp[32][32], undo[32][32]; 245 | 246 | if (go) { 247 | memset(&temp, 0, sizeof(temp)); 248 | 249 | memmove(&temp, &pixel, sizeof(temp)); 250 | memmove(&pixel, &undo, sizeof(pixel)); 251 | memmove(&undo, &temp, sizeof(undo)); 252 | 253 | return; 254 | } 255 | 256 | /* Otherwise, update the buffer */ 257 | memmove(&undo, &pixel, sizeof(undo)); 258 | } 259 | 260 | static void 261 | fill_region(int y, int x, int color, int target) 262 | { 263 | 264 | if (y < 4 || y > (extended ? 36 : (small ? 12 : 20))) 265 | return; 266 | if (x < 32 || x > (extended ? 64 : (small ? 40 : 48))) 267 | return; 268 | 269 | if (color == target) 270 | return; 271 | else if (pixel[y - 4][x - 32].color != target) 272 | return; 273 | else 274 | pixel[y - 4][x - 32].color = color; 275 | 276 | fill_region(y + 1, x, color, target); 277 | fill_region(y - 1, x, color, target); 278 | fill_region(y, x - 1, color, target); 279 | fill_region(y, x + 1, color, target); 280 | } 281 | 282 | static void 283 | file_export(int y, int x) 284 | { 285 | FILE *fp; 286 | char buf[PATH_MAX]; 287 | int code, height, i, j, k = 0, width; 288 | png_structp png_ptr = NULL; 289 | png_infop info_ptr = NULL; 290 | png_byte row[64], extended_row[128]; 291 | png_text title_text; 292 | 293 | height = (extended ? 32 : (small ? 8 : 16)); 294 | width = (extended ? 32 : (small ? 8 : 16)); 295 | 296 | memset(buf, 0, sizeof(buf)); 297 | 298 | move(21 + (extended ? 16 : (small ? -8 : 0)), 31); 299 | printw("Name: "); 300 | echo(); 301 | getnstr(buf, sizeof(buf) - 1); 302 | noecho(); 303 | 304 | if ((fp = fopen(buf, "w+")) == NULL) { 305 | move(21 + (extended ? 16 : (small ? -8 : 0)), 31); 306 | printw("Error: could not open %s for writing", buf); 307 | goto out; 308 | } 309 | 310 | if ((png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) == NULL) { 311 | move(21 + (extended ? 16 : (small ? -8 : 0)), 31); 312 | printw("Error: could not allocate png write struct"); 313 | (void) fclose(fp); 314 | goto out; 315 | } 316 | 317 | if ((info_ptr = png_create_info_struct(png_ptr)) == NULL) { 318 | move(21 + (extended ? 16 : (small ? -8 : 0)), 31); 319 | printw("Error: could not allocate png info struct"); 320 | (void) fclose(fp); 321 | goto out; 322 | } 323 | 324 | if (setjmp(png_jmpbuf(png_ptr))) { 325 | move(21 + (extended ? 16 : (small ? -8 : 0)), 31); 326 | printw("Error: could not create png"); 327 | (void) fclose(fp); 328 | goto out; 329 | } 330 | 331 | png_init_io(png_ptr, fp); 332 | 333 | png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); 334 | 335 | title_text.compression = PNG_TEXT_COMPRESSION_NONE; 336 | title_text.key = "Title"; 337 | title_text.text = buf; 338 | png_set_text(png_ptr, info_ptr, &title_text, 1); 339 | 340 | png_write_info(png_ptr, info_ptr); 341 | 342 | for (i = 0; i < height; i++) { 343 | k = 0; 344 | for (j = 0; j < width; j++) { 345 | if (extended) { 346 | extended_row[k++] = (colors[pixel[i][j].color] >> 16) & 0xff; 347 | extended_row[k++] = (colors[pixel[i][j].color] >> 8) & 0xff; 348 | extended_row[k++] = colors[pixel[i][j].color] & 0xff; 349 | if (pixel[i][j].color == -1) 350 | extended_row[k++] = 0; 351 | else 352 | extended_row[k++] = 0xff; 353 | } else { 354 | row[k++] = (colors[pixel[i][j].color] >> 16) & 0xff; 355 | row[k++] = (colors[pixel[i][j].color] >> 8) & 0xff; 356 | row[k++] = colors[pixel[i][j].color] & 0xff; 357 | if (pixel[i][j].color == -1) 358 | row[k++] = 0; 359 | else 360 | row[k++] = 0xff; 361 | } 362 | } 363 | 364 | if (extended) 365 | png_write_row(png_ptr, extended_row); 366 | else 367 | png_write_row(png_ptr, row); 368 | } 369 | png_write_end(png_ptr, NULL); 370 | 371 | (void) fclose(fp); 372 | 373 | out: 374 | if (info_ptr != NULL) 375 | png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); 376 | if (png_ptr != NULL) 377 | png_destroy_write_struct(&png_ptr, NULL); 378 | 379 | clear(); 380 | scrinit(); 381 | color_panel(); 382 | instructions(); 383 | 384 | move(y, x); 385 | } 386 | 387 | static void 388 | file_save(int y, int x) 389 | { 390 | FILE *fp; 391 | char buf[PATH_MAX]; 392 | int i, j; 393 | 394 | memset(buf, 0, sizeof(buf)); 395 | 396 | move(21 + (extended ? 16 : (small ? -8 : 0)), 31); 397 | printw("Name: "); 398 | echo(); 399 | getnstr(buf, sizeof(buf) - 1); 400 | noecho(); 401 | 402 | if ((fp = fopen(buf, "w+")) == NULL) { 403 | move(21 + (extended ? 16 : (small ? -8 : 0)), 31); 404 | printw("Error: could not open %s for writing", buf); 405 | goto out; 406 | } 407 | 408 | for (i = 0; i < 16 + (extended ? 16 : (small ? -8 : 0)); i++) { 409 | for (j = 0; j < 16 + (extended ? 16 : (small ? -8 : 0)); j++) { 410 | if (pixel[i][j].color != -1) 411 | fprintf(fp, "%d,%d,%d\n", pixel[i][j].y, pixel[i][j].x, pixel[i][j].color); 412 | } 413 | } 414 | (void) fclose(fp); 415 | 416 | out: 417 | clear(); 418 | scrinit(); 419 | color_panel(); 420 | instructions(); 421 | 422 | move(y, x); 423 | } 424 | 425 | static void 426 | file_open(const char *fn) 427 | { 428 | FILE *fp; 429 | const char *errstr; 430 | char xbuf[3], ybuf[3], colorbuf[4]; 431 | int c, color, i, x, y; 432 | 433 | if ((fp = fopen(fn, "r")) == NULL) 434 | return; 435 | 436 | while (1) { 437 | i = 0; 438 | while ((c = fgetc(fp)) != ',') { 439 | if (c == EOF) 440 | goto out; 441 | ybuf[i++] = c; 442 | if (i == sizeof(ybuf)) 443 | i = sizeof(ybuf) - 1; 444 | } 445 | ybuf[i] = '\0'; 446 | 447 | i = 0; 448 | while ((c = fgetc(fp)) != ',') { 449 | if (c == EOF) 450 | goto out; 451 | xbuf[i++] = c; 452 | if (i == sizeof(xbuf)) 453 | i = sizeof(xbuf) - 1; 454 | } 455 | xbuf[i] = '\0'; 456 | 457 | i = 0; 458 | while ((c = fgetc(fp)) != '\n') { 459 | if (c == EOF) 460 | goto out; 461 | colorbuf[i++] = c; 462 | if (i == sizeof(colorbuf)) 463 | i = sizeof(colorbuf) - 1; 464 | } 465 | colorbuf[i] = '\0'; 466 | 467 | if (extended) 468 | y = strtonum(ybuf, 0, 31, &errstr); 469 | else if (small) 470 | y = strtonum(ybuf, 0, 7, &errstr); 471 | else 472 | y = strtonum(ybuf, 0, 15, &errstr); 473 | if (errstr != NULL) 474 | return; 475 | 476 | if (extended) 477 | x = strtonum(xbuf, 0, 31, &errstr); 478 | else if (small) 479 | x = strtonum(xbuf, 0, 7, &errstr); 480 | else 481 | x = strtonum(xbuf, 0, 15, &errstr); 482 | if (errstr != NULL) 483 | return; 484 | 485 | color = strtonum(colorbuf, 0, 255, &errstr); 486 | if (errstr != NULL) 487 | return; 488 | 489 | pixel[y][x].y = y; 490 | pixel[y][x].x = x; 491 | pixel[y][x].color = color; 492 | } 493 | 494 | out: 495 | (void) fclose(fp); 496 | 497 | draw_screen(0, 0, 0); 498 | } 499 | 500 | static void 501 | confirm_quit(int y, int x) 502 | { 503 | int c; 504 | 505 | again: 506 | move(21 + (extended ? 16 : (small ? -8 : 0)), 31); 507 | printw("Save? [y/n]: "); 508 | echo(); 509 | c = getch(); 510 | noecho(); 511 | 512 | move(21 + (extended ? 16 : (small ? -8 : 0)), 31); 513 | printw(" "); 514 | 515 | if (c == 'Y' || c == 'y') 516 | file_save(y, x); 517 | else if (c != 'N' && c != 'n') 518 | goto again; 519 | } 520 | 521 | static void 522 | main_loop(void) 523 | { 524 | int c, color = 0, dirty = 0, lock = 0, loop = 1, o, x, y; 525 | 526 | x = (extended ? 47 : (small ? 35 : 39)); 527 | y = (extended ? 19 : (small ? 7 : 11)); 528 | 529 | attron(COLOR_PAIR(color)); 530 | mvaddch(y, x, ' '); 531 | attroff(COLOR_PAIR(color)); 532 | refresh(); 533 | 534 | while (loop) { 535 | switch ((c = getch())) { 536 | case '/': 537 | if ((lock = !lock)) { 538 | do_undo(0); 539 | goto print; 540 | } 541 | break; 542 | case KEY_UP: 543 | case 'K': 544 | case 'k': 545 | if (--y < 4) 546 | y = 4; 547 | if (lock) 548 | goto print; 549 | break; 550 | case KEY_DOWN: 551 | case 'J': 552 | case 'j': 553 | if (++y > (extended ? 35 : (small ? 11 : 19))) 554 | y = (extended ? 35 : (small ? 11 : 19)); 555 | if (lock) 556 | goto print; 557 | break; 558 | case KEY_LEFT: 559 | case 'H': 560 | case 'h': 561 | if (--x < 32) 562 | x = 32; 563 | if (lock) 564 | goto print; 565 | break; 566 | case KEY_RIGHT: 567 | case 'L': 568 | case 'l': 569 | if (++x > (extended ? 63 : (small ? 39 : 47))) 570 | x = (extended ? 63 : (small ? 39 : 47)); 571 | if (lock) 572 | goto print; 573 | break; 574 | case ' ': 575 | do_undo(0); 576 | print: 577 | pixel[y - 4][x - 32].color = color; 578 | dirty = 1; 579 | break; 580 | case 'C': 581 | case 'c': 582 | color = change_color(y, x, color); 583 | if (lock) 584 | goto print; 585 | break; 586 | case 'D': 587 | case 'd': 588 | do_undo(0); 589 | pixel[y - 4][x - 32].color = -1; 590 | dirty = 1; 591 | break; 592 | case 'E': 593 | case 'e': 594 | file_export(y, x); 595 | break; 596 | case 'F': 597 | case 'f': 598 | do_undo(0); 599 | fill_region(y, x, color, pixel[y - 4][x - 32].color); 600 | dirty = 1; 601 | break; 602 | case 'S': 603 | case 's': 604 | file_save(y, x); 605 | dirty = 0; 606 | break; 607 | case 'Q': 608 | case 'q': 609 | if (dirty == 1) 610 | confirm_quit(y, x); 611 | loop = 0; 612 | break; 613 | case 'U': 614 | case 'u': 615 | do_undo(1); 616 | } 617 | 618 | draw_screen(y, x, color); 619 | } 620 | } 621 | 622 | static void 623 | usage(void) 624 | { 625 | 626 | fprintf(stderr, "usage: %s [-es] [file]\n", getprogname()); 627 | 628 | exit(1); 629 | } 630 | 631 | int 632 | main(int argc, char *argv[]) 633 | { 634 | int ch; 635 | 636 | while ((ch = getopt(argc, argv, "es")) != -1) { 637 | switch (ch) { 638 | case 'e': 639 | extended = 1; 640 | small = 0; 641 | break; 642 | case 's': 643 | extended = 0; 644 | small = 1; 645 | break; 646 | default: 647 | usage(); 648 | } 649 | } 650 | argc -= optind; 651 | argv += optind; 652 | 653 | if (argc > 1) 654 | usage(); 655 | 656 | initscr(); 657 | clear(); 658 | keypad(stdscr, TRUE); 659 | cbreak(); 660 | noecho(); 661 | curs_set(0); 662 | 663 | scrinit(); 664 | color_panel(); 665 | instructions(); 666 | 667 | init_pixels(); 668 | 669 | draw_transparency(); 670 | 671 | /* Set up undo structures */ 672 | do_undo(0); 673 | 674 | if (argc == 1) 675 | file_open(*argv); 676 | 677 | main_loop(); 678 | 679 | endwin(); 680 | 681 | return 0; 682 | } 683 | -------------------------------------------------------------------------------- /sprite.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2021 Brian Callahan 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef HAVE_GETPROGNAME 18 | extern const char *getprogname(void); 19 | #endif 20 | 21 | #ifndef HAVE_STRTONUM 22 | extern long long strtonum(const char *, long long, long long, const char **); 23 | #endif 24 | -------------------------------------------------------------------------------- /strtonum.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: strtonum.c,v 1.8 2015/09/13 08:31:48 guenther Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2004 Ted Unangst and Todd Miller 5 | * All rights reserved. 6 | * 7 | * Permission to use, copy, modify, and distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #ifndef HAVE_STRTONUM 25 | 26 | #define INVALID 1 27 | #define TOOSMALL 2 28 | #define TOOLARGE 3 29 | 30 | long long 31 | strtonum(const char *numstr, long long minval, long long maxval, 32 | const char **errstrp) 33 | { 34 | long long ll = 0; 35 | int error = 0; 36 | char *ep; 37 | struct errval { 38 | const char *errstr; 39 | int err; 40 | } ev[4] = { 41 | { NULL, 0 }, 42 | { "invalid", EINVAL }, 43 | { "too small", ERANGE }, 44 | { "too large", ERANGE }, 45 | }; 46 | 47 | ev[0].err = errno; 48 | errno = 0; 49 | if (minval > maxval) { 50 | error = INVALID; 51 | } else { 52 | ll = strtoll(numstr, &ep, 10); 53 | if (numstr == ep || *ep != '\0') 54 | error = INVALID; 55 | else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) 56 | error = TOOSMALL; 57 | else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) 58 | error = TOOLARGE; 59 | } 60 | if (errstrp != NULL) 61 | *errstrp = ev[error].errstr; 62 | errno = ev[error].err; 63 | if (error) 64 | ll = 0; 65 | 66 | return (ll); 67 | } 68 | 69 | #endif /* !HAVE_STRTONUM */ 70 | --------------------------------------------------------------------------------