|
|
1.1 root 1: .so ../ADM/mac
2: .XX pico 423 "Pico \(em A Language For Composing Digital Images"
3: .nr dP 2
4: .nr dV 3p
5: .EQ
6: delim @#
7: .EN
8: .ND July, 1989
9: .TL
10: Pico \(em A Language For Composing Digital Images
11: .AU "MH 2C-521" 6335
12: Gerard J. Holzmann
13: .AI
14: .MH
15: .AB
16: \fIPico\fP is a small expression language for picture compositions.
17: It can be used either interactively with a display or stand alone as a
18: picture file editor.
19: The following \fIpico\fP script, for instance, turns an arbitrary digitized
20: image stored in a file
21: .CW in
22: upside down, rotates it by 90 degrees,
23: and writes its negative into a file
24: .CW out :
25: .P1 20n
26: $ pico
27: 1: new = Z-$in[X-y,x]
28: 2: w out
29: 3: q
30: $
31: .P2
32: .PP
33: Numerous examples of
34: .I pico
35: transformations of pictures are included throughout this volume.
36: .AE
37: .2C
38: .NH
39: Black&White Images
40: .PP
41: The pictures that can be manipulated by \fIpico\fP are stored as
42: regular files in the picture file format described in
43: .I picfile (5).
44: The picture editor is most conveniently used interactively
45: with a Metheus frame buffer display.
46: The result of picture transformations is then directly visible
47: and can be used to correct or enhance the mistakes that one is
48: bound to make.
49: .SH
50: New and Old
51: .PP
52: Assuming that you want to work interactively and have access to one
53: of the Metheus frame buffers, e.g. on
54: .CW pipe ,
55: a session with \fIpico\fP can be started by typing
56: .P1 0
57: $ pico -m\fIN\fP
58: b&w, 512x512 pel, Metheus display
59: 1:
60: .P2
61: where
62: .I N
63: is the device number of the frame buffer to be used.
64: By default,
65: .I N
66: is zero and opens
67: .CW /dev/om0 .
68: Also by default the size of the workarea is 512x512 pixels.
69: If you need to work with larger images, you
70: can override the default by explicitly setting a different
71: window width and height, for instance:
72: .P1 0
73: $ pico -m5 -w1024 -h1024
74: b&w, 1024x1024 pel, Metheus display
75: 1:
76: .P2
77: The maximum size image that the Metheus frame buffer
78: can display is 1280x1024 pixels.
79: .PP
80: The number followed by a colon in the example above
81: is \fIpico\fP's prompt for commands.
82: .PP
83: The result of the last edit operation (initially an all black image)
84: is accessible under the predefined name
85: .CW old ,
86: and
87: the destination of the image transformations is known as
88: .CW new .
89: To quickly get a picture into the workbuffer, you can use the command
90: .CW get
91: followed by the name of the file with the image.
92: .P1 0
93: 1: get "pjw"
94: .P2
95: The most frequently used command in \fIpico\fP is
96: .CW x ,
97: short for
98: .CW execute .
99: To make a black&white negative from the current picture
100: the command would be:
101: .P1 0
102: 2: x new=Z-old
103: .P2
104: where Z is a predefined constant with the value of maximum white (255).
105: By default the transformation is applied to every pixel on the screen.
106: .PP
107: Assume we have two image files with portraits.
108: We can open these files by using
109: .CW get ,
110: or we can specify them
111: on the \fIpico\fP command line, as follows, using
112: .CW /dev/om5 :
113: .P1 0
114: $ pico -m5 ./face/rob ./face/pjw
115: b&w, 512x512 pel, Metheus display
116: 1:
117: .P2
118: We can create a new image, for instance, by averaging the two faces:
119: .P1 0
120: $ pico -m5 ./face/rob ./face/pjw
121: b&w, 512x512 pel, Metheus display
122: 1: x new=($rob+$pjw)/2
123: 2:
124: .P2
125: The transformation is written as an assignment of an
126: expression to the destination
127: .CW new .
128: Names preceded by a dollar sign refer to picture files,
129: for instance as specified on the command line.
130: A long name such as
131: .CW ./face/rob
132: can be abbreviated
133: to its base name
134: .CW rob
135: (the part following the last slash).
136: Not all file names have to be provided on the command line though.
137: We can also
138: .CW append
139: a new file
140: .CW doug ,
141: without reading it, by typing:
142: .P1 0
143: 2: a "doug"
144: .P2
145: The double quotes are necessary.
146: They avoid confusion when, for instance,
147: .CW /
148: symbols
149: are part of the filename.
150: We can check which files are currently open by typing
151: .CW f :
152: .P1 0
153: 3: f
154: $0: old color resident
155: $1: rob b&w resident
156: $2: pjw b&w resident
157: $3: doug b&w absent
158: .P2
159: The numbers in the first column serve as a shorthand for the file names.
160: Typing
161: .CW $1
162: therefore is equivalent to typing
163: .CW $rob .
164: We will use both notations
165: .CW $1
166: and
167: .CW $rob
168: below.
169: The first line
170: .CW $0
171: is a shorthand for
172: .CW old
173: and refers to
174: the workarea (the screen in interactive usage).
175: .PP
176: We have a black and white image on the screen that is an average
177: of the two files
178: .CW rob
179: and
180: .CW pjw .
181: To see
182: .CW rob
183: separately we could type:
184: .P1 0
185: 4: x new=$1
186: .P2
187: but that is hardly an inspiring procedure.
188: Let's just take the left half
189: or rob's face combined with the right half of peter's:
190: .P1 0
191: 5: x new=(x<256)?$rob:$pjw
192: .P2
193: or to make a mirror image:
194: .P1 0
195: 5: x new=(x<256)?$rob[x,y]:$rob[X-x,y]
196: .P2
197: The variable
198: .CW x
199: used in the expression is predefined.
200: Don't confuse it with the first
201: .CW x
202: on the command line
203: which identifies the type of command to
204: be executed.
205: The variable
206: .CW x
207: gives the current x-coordinate of a pixel
208: during transformations.
209: Since in this case 512 pixels fit on one scan line, a pixel in
210: the middle of the screen has an
211: .CW x -coordinate
212: of 256.
213: The maximum value of
214: .CW x
215: is given by a predefined variable
216: .CW X .
217: .CW X/2 ,
218: therefore, is a safer way to specify the middle of a scan line.
219: .PP
220: The expression above is a conditional of the form:
221: .P1 0
222: \fIcondition\fP ? \fIiftrue\fP : \fIiffalse\fP
223: .P2
224: For every screen position where the condition holds the
225: .I iftrue
226: part
227: of the expression applies and everywhere else the
228: .I iffalse
229: part applies.
230: Another predefined variable of this type is
231: .CW y
232: (the y-coordinate of the destination).
233: The maximum
234: .CW y
235: value is called
236: .CW Y .
237: Since
238: .CW $0
239: refers to the screen we can turn the picture
240: on the screen upside down by typing:
241: .P1 0
242: 6: x new = $0[x, Y-y]
243: .P2
244: All pixels in the black&white picture are internally represented by a
245: value in the range 0..255, where 0 means black and 255 means white.
246: To reverse an image, therefore it would suffice to subtract the current
247: value of each pixel from its maximum value 255, which is stored in constant
248: .CW Z .
249: Getting very bold we can turn the picture on its side,
250: and make it negative by saying:
251: .P1 0
252: 8: x new = 255 - old[y,511-x]
253: .P2
254: or, slightly more abstract
255: .P1 0
256: 9: x new = Z - old[y,Y-x]
257: .P2
258: Note that we swapped x and y to turn the picture on its side.
259: Nothing can stop us now:
260: .P1 0
261: 10: x new=(x<X/3)?$1:(x>X*2/3)?$2: \*(cr
262: 3*((x-X/3)*$2+(X*2/3-x)*$1)/X
263: .P2
264: fades
265: .CW rob
266: slowly into
267: .CW pjw .
268: (We have used \*(cr to break the line into
269: two pieces for lay-out purposes.
270: When using \fIpico\fP, it should be typed as one complete line.)
271: Actually this last transformation is easier to read as a little \fIpico\fP
272: program.
273: To see how this works, and what the defaults in the above expression
274: are, the above expression could be typed as:
275: .P1 0
276: 10: x {
277: int L, R
278:
279: L = X/3; R = X*2/3
280:
281: for (y = 0; y < Y; y++)
282: for (x = 0; x < X; x++)
283: { if (x < L)
284: new[x,y] = $1
285: else if (x > R)
286: new[x,y] = $2
287: else
288: new[x,y] = 3*((x-L)* \*(cr
289: $2+(R-x)*$1)/X
290: }
291: }
292: .P2
293: There fewer defaults here, though an assignment to
294: .CW new
295: is still
296: interpreted as a parallel assignment to all three color channels in the
297: picture.
298: You can override also these defaults by making the program still more explicit,
299: for instance by using the suffixes
300: .CW red ,
301: .CW grn ,
302: and
303: .CW blu
304: to access
305: color channels separately:
306: .CW new[x,y].red ,
307: .CW new[x,y].grn ,
308: and
309: .CW new[x,y].blu .
310: To see the effect you need to set the workbuffer to color mode first
311: with the command
312: .CW color .
313: (You go back to the default black&white mode with the command
314: .CW nocolor ).
315: .PP
316: All normal arithmetic operators from C are available.
317: The
318: .CW ^
319: operator, for instance, makes an
320: .I "exclusive or"
321: of its operands.
322: Thus,
323: .P1 0
324: 11: x new=x^y^$rob
325: .P2
326: is a particularly striking effect, and
327: .P1 0
328: 13: x new = $rob +(Z - $rob[x+2, y+2])
329: .P2
330: is an attempt to make a relief.
331: .PP
332: There is no range checking on explicit or implicit array indexing.
333: The use of
334: .CW x+2
335: in the last expression is therefore risky
336: and is best protected with a conditional:
337: .P1 0
338: 13: x new=$rob+(Z-(x<509 && y<509)? \*(cr
339: $rob[x+2,y+2]:Z)
340: .P2
341: or more conveniently with the builtins
342: .CW xclamp
343: and
344: .CW yclamp :
345: .P1 0
346: 14: x new=$rob+(Z-$rob[xclamp(x+2), \*(cr
347: yclamp(y+2)])
348: .P2
349: Another promising attempt to make a core dump would be to type
350: something like
351: .P1 0
352: 14: x new=$rob[x*y, x/y]
353: .P2
354: .NH
355: Color Images
356: .PP
357: A complete picture specifies pixel values for each of three
358: separate color channels: red, green, and blue.
359: When the editor is used in black&white mode only the
360: red channel is used.
361: When a black&white picture is converted to color mode,
362: all three channels are made equal.
363: The omission of a channel suffix to
364: .CW old ,
365: .CW new
366: or a file name
367: is similarly interpreted to mean that a transformation expression will
368: apply equally to all three color channels.
369: By specifying an explicit suffix
370: .CW red ,
371: .CW grn ,
372: or
373: .CW blu ,
374: however, we can write each channel separately.
375: So:
376: .P1 0
377: 14: color
378: 15: x new.red=$rob
379: 16: x new.grn=$rob
380: 17: x new.blu=255-$rob
381: .P2
382: will write
383: .CW rob
384: on the red and green channels, and its negative on the
385: blue channel.
386: If
387: .CW rob
388: is a black&white picture then typing
389: .CW $rob
390: is, of course,
391: equivalent to typing
392: .CW $rob.red .
393: We could also have combined the first two lines in a chain assignment:
394: .P1 0
395: 18: x new.red=new.grn=$rob
396: .P2
397: We can also write a separate value to each channel
398: by using
399: .CW composites .
400: A color composite is written as a comma separated list
401: of three values, enclosed in square brackets:
402: .P1 0
403: 19: x new.rgb=[$rob,$rob,Z-$rob ]
404: .P2
405: The channels are addressed by the three fields of the composite in
406: the order: [red, green, blue].
407: Omitting to specify a composite when an
408: .CW rgb
409: destination is
410: used typically results in only the red channel being written.
411: As expected,
412: .P1 0
413: 20: x new.rgb=[old.grn,old.blu,old.red ]
414: .P2
415: rotates the colors of the picture.
416: And, of course, you can freely combine
417: the color suffixes with array indexing:
418: .CW $rob.blu
419: is just a shorthand
420: for
421: .CW "$rob[x, y].blu "
422: where the variables
423: .CW x ,
424: and
425: .CW y
426: can be replaced by just any monstrous C-style expression.
427: .NH
428: The Color Maps
429: .PP
430: When working interactively,
431: the color map in the Metheus frame buffer display
432: can be set with one of the commands
433: .CW cmap
434: (all channels),
435: .CW cmap.red ,
436: .CW cmap.grn , or
437: .CW cmap.blu .
438: The color map is a mapping table that can arbitrarily map
439: pixel brightness values in the range 0..255 to other
440: brightness values, within the same range 0..255.
441: The update, however, only happens on the screen and is not stored
442: when the image file is written.
443: The variable
444: .CW i
445: is used to index the color map.
446: For instance:
447: .P1 0
448: 21: x cmap = Z-i
449: .P2
450: will very quickly make a negative, and
451: .P1 0
452: 22: x cmap = i
453: .P2
454: turns the picture back to normal.
455: To fake color in a black and white image you can try:
456: .P1 0
457: 23: x cmap.red = (i<=85)?i:0
458: 24: x cmap.grn = (i>85 && i<170)?i:0
459: 25: x cmap.blu = (i>=170)?i:0
460: .P2
461: Remember that changing the color map only changes the appearance
462: of the picture on the screen, not its definition in memory.
463: .NH
464: Read, Write, and Windows
465: .PP
466: The
467: .CW append
468: command, to add files to the list of dollar arguments
469: was discussed before.
470: Using the command
471: .CW get
472: instead of
473: .CW a
474: will also put the file into $0,
475: that is on the screen.
476: To save the current state of the display in a file, use:
477: .P1 0
478: 26: w filename
479: .P2
480: A raw black&white picture file, without the picture file header,
481: can be written by using
482: .CW "w -"
483: instead of
484: .CW w
485: (for instance when dumping a file to be processed
486: by software uneducated in picture file headers).
487: The size of the file written conforms to the
488: current window size of the editor (see also below).
489: To close a no longer used file and free up some memory for others, say:
490: .P1 0
491: 27: d doug
492: .P2
493: or
494: .P1 0
495: 27: d $1
496: .P2
497: giving the file's base name or its dollar number.
498: To restrict the updates to a window on the screen you can set:
499: .P1 0
500: 30: window 10 100 200 300
501: .P2
502: which makes a window with origin at (x,y) = (10,100), 200 pixels wide
503: and 300 pixels deep.
504: And, if you really want to exit \fIpico\fP you can type a control-D
505: or resort to the
506: .CW quit
507: command:
508: .P1 0
509: 32: q
510: .P2
511: .NH
512: Programs
513: .PP
514: As shown in one of the examples above, it is possible to write
515: small \fIpico\fP programs for the more difficult transformations
516: that cannot be handled by the defaults.
517: The control structure for the \fIpico\fP programs is again stolen from C.
518: .PP
519: A \fIpico\fP program starts with a left curly brace
520: .CW {
521: followed by
522: zero or more declarations of (long) integers or arrays.
523: For instance,
524: .P1 0
525: 33: x {
526: int a, b; array ken[100]
527:
528: ...
529: .P2
530: Note carefully that the left curly brace turns off the default control
531: flow over all the pixels in a picture; the control flow in a program
532: must be specified explicitly.
533: The above program fragment declares two local integers
534: .CW a
535: and
536: .CW b
537: which
538: by default will be initialized to zero,
539: and an array of 100 (long) integers named
540: .CW ken ,
541: also initialized to zeros.
542: To initialize it to another value, use constants:
543: .P1 0
544: int a = 9
545: .P2
546: Statements can either be separated by newlines or by explicit semicolons.
547: Here then is a list of valid types of statements:
548: .P1 0
549: \fIlvalue\fP = \fIexpr\fP
550: if (\fIexpr\fP) \fIstmnt\fP
551: if (\fIexpr\fP) \fIstmnt\fP else \fIstmnt\fP
552: for (\fIexpr\fP; \fIexpr\fP; \fIexpr\fP) \fIstmnt\fP
553: while (\fIexpr\fP) \fIstmnt\fP
554: do \fIstmnt\fP while (\fIexpr\fP)
555: \fIlabel\fP: \fIstmnt\fP
556: goto label
557: { \fIstmnt\fP }
558: .P2
559: An \fIlvalue\fP is an explicitly declared local variable or array element,
560: one of the predefined variables
561: .CW x ,
562: or
563: .CW y ,
564: or a picture
565: element.
566: A picture element can again be a file name such as
567: .CW $doug
568: with a
569: default selection of the pixel inside it,
570: .CW $doug[x,y] ,
571: or it can be
572: more elaborate as in
573: .CW "$doug[x/2, y<<1].red" .
574: The destination of the transformation, is again referred to by
575: the keyword
576: .CW new ,
577: but this time it needs an explicit array indexing.
578: The equivalent of
579: .P1 0
580: 34: x new=old[y,x]
581: .P2
582: to turn the picture on its side, can be written as a \fIpico\fP program:
583: .P1 0
584: 35: x {
585: for (y = 0; y < 512; y++)
586: for (x = 0; x < 512; x++)
587: new[x,y] = old[y,x]
588: }
589: .P2
590: Note that also the control flow must be explicit.
591: Typing only
592: .P1 0
593: 36: x { new[x,y] = old[y,x] }
594: .P2
595: would use the initial (zero) values of
596: .CW x
597: and
598: .CW y
599: and
600: merely assign
601: .CW "new[0,0] = old[0,0]" .
602: .NH
603: Array Indexing and Control Flow Defaults
604: .PP
605: One more word about defaults.
606: \fIPico\fP tries to be smart about assigning types to values.
607: When a single rvalue is needed and a color composite is available
608: and average of the color channels is the default, for instance:
609: .P1 0
610: .CW old becomes
611: .CW (old[x,y].red+old[x,y].grn+old[x,y].blu)/3 .
612: .P2
613: If on the other hand a value is available and a composite is
614: needed the value will be replicated into a fake composite.
615: To override the defaults assignments can of course always
616: be made more explicit.
617: Normal cases should work as expected, for instance, by default:
618: .P1 0
619: new = old
620: .P2
621: truly means
622: .P1 0
623: new.red=old.red;
624: new.grn=old.grn;
625: new.blu=old.blu
626: .P2
627: .NH
628: Procedures
629: .PP
630: There is a facility in \fIpico\fP to declare named segments of code and
631: use these as functions or procedures.
632: As an example, the following command declares a procedure
633: .CW doit
634: that makes a histogram of a window of pixels on the screen.
635: It is equivalent to the sequence:
636: .P1 0
637: 37: window a b w d
638: 38: x { global array histog[256]; }
639: 39: x histo[old]++
640: 40: window 0 0 512 512
641: .P2
642: In a procedure this is written as:
643: .P1 0
644: 37: def doit(a, b, w, d) {
645: global array histog[256]
646:
647: for (y = a; y < a+w; y++)
648: for (x = b; x < b+d; x++)
649: histog[old[x,y]]++
650: }
651: .P2
652: The declaration prefix
653: .CW global
654: extends the scope of an array so that it can be
655: referred to in subsequent procedures, programs or expressions.
656: We can now call the procedure and use the histogram to arbitrarily
657: change the color map:
658: .P1 0
659: 38: x { doit(0,0,512,512); }
660: 39: x cmap = histog[i]%256
661: .P2
662: Or more usefully, to calculate and apply a simple
663: histogram equalization:
664: .P1 0
665: 40: x {
666: int ave, i, j, R, L, Hint;
667: global array eqlz[256]
668:
669: .P3
670: for (i = ave = 0; i < 256; i++)
671: ave += histog[i];
672: ave /= 256
673:
674: .P3
675: for (i = R = Hint = 0; i < 256; i++)
676: { L = R
677: Hint += histog[i]
678: while (Hint > ave)
679: { Hint -= ave
680: R++
681: }
682: j = (L+R)/2
683: eqlz[i] = (j>255)?255:j
684: }
685: }
686: 41: x new=eqlz[old]
687: .P2
688: When \fIpico\fP starts up it reads a small set of library procedures
689: by simulating an
690: .CW r
691: command (see Table 1).
692: These definitions are stored in the file
693: .CW /usr/lib/pico/defines .
694: The builtins listed in Table 2 can be used in \fIpico\fP programs and
695: \fIpico\fP procedures.
696: .NH
697: Non-Interactive Use of \fIPico\fP
698: .PP
699: When \fIpico\fP is used without having access to a frame buffer
700: all commands will still work.
701: To check the result of executing commands
702: it can be convenient to write what would have been the current screen image
703: into a file with the
704: .CW w
705: command
706: and view it in another \fImux\fP-window on the 5620 (or 630) terminal with
707: a command like
708: .I flicks .
709: For instance, in one window the session can be:
710: .P1 0
711: $ pico rob pjw
712: 1: nocolor
713: 2: x new=($rob*$pjw)/255
714: 3: w - junk
715: 4:
716: .P2
717: While in another window the file is displayed
718: with
719: .I flicks ,
720: as follows:
721: .P1 0
722: $ flicks -em junk
723: .P2
724: .NH
725: See Also
726: .PP
727: More examples on how to use \fIpico\fP, and on its internal structure,
728: can be found in:
729: |reference(pico %no_cite)|reference(digital darkroom %no_cite)
730: .PP
731: |reference_placement
732: .FC
733: .1C
734: .TS
735: center;
736: c c
737: lFCW lw(4i).
738: _
739: COMMAND DESCRIPTION
740: =
741: a [x y w d] file T{
742: append file with optional offset/dimensions
743: T}
744: d basename/$n delete file
745: h file T{
746: read header information from file(s)
747: T}
748: r file read (library) command file
749: w [-] file T{
750: write file or window with or without a header
751: format: default = \fIpico\fP header, \- means no header
752: T}
753: _
754: nocolor T{
755: update & display only 1 channel
756: T}
757: color T{
758: update & display all 3 channels
759: T}
760: _
761: window x y w d T{
762: restrict workarea to this window
763: T}
764: _
765: get [x y w d] file T{
766: read file contents into
767: .CW old
768: T}
769: get $n T{
770: refresh
771: .CW old
772: with an already opened file
773: T}
774: _
775: f show mounted files
776: show [name] T{
777: show symbol information (values)
778: T}
779: functions show functions
780: _
781: def name { pprog } define a function
782: x expr execute expr in default loop
783: x { pprog } execute \fIpico\fP program
784: _
785: q quit
786: _
787: .TE
788: .ce
789: File names containing nonalphanumeric characters (period, slash) must be enclosed in double quotes.
790: .sp .5
791: .ce
792: \fBTable 1.\fR Command Summary
793: .SP 3
794: .TS
795: center;
796: lFCW lw(4i).
797: _
798: printf(string, args) recognizes only: \f(CW%d\fP, \f(CW%s\fP, \f(CW\en\fP, \f(CW\et\fP
799: x_cart(radius, angle) T{
800: convert radius and angle (degrees: 0..360) into x_coordinate
801: T}
802: y_cart(radius, angle) T{
803: convert radius and angle (degrees: 0..360) into y_coordinate
804: T}
805: X_cart(r,a),Y_cart(r,a) T{
806: same as [\fIxy\fP]_\fIcart\fP, but expects \fIangle\fP in centidegrees
807: T}
808: r_polar(x,y) convert \fIx,y\fP coordinate into radius
809: a_polar(x,y) T{
810: angle returned is in degrees: 0..360, acuuracy=\(+- 2 degrees
811: T}
812: A_polar(x,y) angle returned is in centidegrees: 0..36000
813: putframe(nr) dump window into the file \f(CW"frame.%6d"\fP, \fInr\fP
814: getframe(nr) T{
815: read from the file \f(CW"frame.%6d"\fP, \fInr\fP
816: T}
817: setcmap(i, r,g,b) write \fIi\fPth value in colormap
818: getcmap(i, r,g,b) read \fIi\fPth value in colormap
819: redcmap(i, z)
820: grncmap(), blucmap()
821: _
822: sin(angle), cos(angle) T{
823: returns 0..10,000, \fIangle\fP in degrees : 0..360
824: T}
825: Sin(angle), Cos(angle) T{
826: same but expects \fIangle\fP in centidegrees: 0..36000
827: T}
828: atan(x, y) T{
829: arc-tangent of \fIy/x\fP, returns angle in degrees: 0..360
830: T}
831: exp(a) as advertised
832: log(a),log10(a) returns 1024*result
833: sqrt(a) integer square root of \fIa\fP
834: pow(a,b) \fIa\fP to the power \fIb\fP
835: rand() returns a random integer \fIr\fP: 0\(<=\fIr\fP<32768.
836: _
837: .TE
838: .ce
839: \fBTable 2.\fR Builtin Procedures
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.