| 315 | | (defvar *image-tree-node-counter*) |
|---|
| 316 | | |
|---|
| 317 | | (defmacro with-image-tree-node-counter (&body body) |
|---|
| 318 | | "Allows to call IMAGE-TREE-NODE-UNIQUE-NAME in BODY." |
|---|
| 319 | | `(let ((*image-tree-node-counter* -1)) |
|---|
| 320 | | ,@body)) |
|---|
| 321 | | |
|---|
| 322 | | (defun image-tree-node-unique-name () |
|---|
| 323 | | "Generates a unique name for an image-tree-node." |
|---|
| 324 | | (format nil "image-tree-~a-~a-~a" (get-universal-time) (random 10000) (incf *image-tree-node-counter*))) |
|---|
| 325 | | |
|---|
| 326 | | (defpersistent-class image-tree-node (store-image) |
|---|
| 327 | | ((geo-x :initarg :geo-x :reader geo-x) |
|---|
| 328 | | (geo-y :initarg :geo-y :reader geo-y) |
|---|
| 329 | | (geo-width :initarg :geo-width :reader geo-width) |
|---|
| 330 | | (geo-height :initarg :geo-height :reader geo-height) |
|---|
| 331 | | (children :initarg :children :reader children) |
|---|
| 332 | | (parent :reader parent) |
|---|
| 333 | | (depth :accessor depth :initarg :depth)) |
|---|
| 334 | | (:documentation "Derived from STORE-IMAGE, IMAGE-TREE-NODE is an |
|---|
| 335 | | image itself, which has additional information, like its |
|---|
| 336 | | geo-location. It also knows about its position in the tree; being at a |
|---|
| 337 | | certain DEPTH and pointing to its PARENT and its CHILDREN.")) |
|---|
| 338 | | |
|---|
| 339 | | (defpersistent-class image-tree (image-tree-node) |
|---|
| 340 | | ((parent :initform nil)) |
|---|
| 341 | | (:documentation "IMAGE-TREE is the root node of IMAGE-TREE-NODEs.")) |
|---|
| 342 | | |
|---|
| 343 | | (defmethod print-object ((object image-tree-node) stream) |
|---|
| 344 | | (print-unreadable-object (object stream :type t) |
|---|
| 345 | | (format stream "ID: ~A (~A x ~A)" |
|---|
| 346 | | (store-object-id object) |
|---|
| 347 | | (store-image-width object) |
|---|
| 348 | | (store-image-height object)))) |
|---|
| 349 | | |
|---|
| 350 | | (defmethod initialize-persistent-instance :after ((obj image-tree-node)) |
|---|
| 351 | | ;; initialize the parent slot |
|---|
| 352 | | (dolist (child (children obj)) |
|---|
| 353 | | (setf (slot-value child 'parent) obj))) |
|---|
| 354 | | |
|---|
| 355 | | (defmethod geo-location ((obj image-tree-node)) |
|---|
| 356 | | (list (geo-x obj) (geo-y obj) (geo-width obj) (geo-height obj))) |
|---|
| 357 | | |
|---|
| 358 | | (defun make-image-tree-node (image &key geo-rect children |
|---|
| 359 | | (class-name 'image-tree-node) |
|---|
| 360 | | depth) |
|---|
| 361 | | (destructuring-bind (geo-x geo-y geo-width geo-height) |
|---|
| 362 | | geo-rect |
|---|
| 363 | | (make-store-image :image image |
|---|
| 364 | | :name (image-tree-node-unique-name) |
|---|
| 365 | | :class-name class-name |
|---|
| 366 | | :initargs `(:geo-x ,geo-x |
|---|
| 367 | | :geo-y ,geo-y |
|---|
| 368 | | :geo-width ,geo-width |
|---|
| 369 | | :geo-height ,geo-height |
|---|
| 370 | | :children ,children |
|---|
| 371 | | :depth ,depth)))) |
|---|
| 372 | | |
|---|
| 373 | | (defun image-tree-node-less (a b) |
|---|
| 374 | | "Allows to give IMAGE-TREE-NODEs a canonical order according to |
|---|
| 375 | | their geo-locations." |
|---|
| 376 | | (cond |
|---|
| 377 | | ((< (geo-x a) (geo-x b)) t) |
|---|
| 378 | | ((= (geo-x a) (geo-x b)) |
|---|
| 379 | | (< (geo-y a) (geo-y b))) |
|---|
| 380 | | (t nil))) |
|---|
| 381 | | |
|---|
| 382 | | ;; (defmethod lod-min ((obj image-tree-node)) |
|---|
| 383 | | ;; (/ (min (store-image-width obj) (store-image-height obj)) 2.0)) |
|---|
| 384 | | |
|---|
| 385 | | ;; (defmethod lod-min ((obj image-tree)) |
|---|
| 386 | | ;; 900) |
|---|
| 387 | | |
|---|
| 388 | | ;; (defmethod lod-max ((obj image-tree-node)) |
|---|
| 389 | | ;; (if (children obj) |
|---|
| 390 | | ;; (* (store-image-width obj) (store-image-height obj)) |
|---|
| 391 | | ;; -1)) |
|---|
| 392 | | |
|---|
| 393 | | (defmethod lod-min ((obj image-tree-node)) |
|---|
| 394 | | "Initially intended to customize LOD-MIN according to the node's |
|---|
| 395 | | context. It seems that a constant default value is sufficient here." |
|---|
| 396 | | 256) |
|---|
| 397 | | |
|---|
| 398 | | (defmethod lod-min ((obj image-tree)) |
|---|
| 399 | | 16) |
|---|
| 400 | | |
|---|
| 401 | | (defmethod lod-max ((obj image-tree-node)) |
|---|
| 402 | | "See LOD-MIN." |
|---|
| 403 | | -1) |
|---|
| 404 | | |
|---|
| 405 | | (defun children-sizes (width height &key (divisor 2)) |
|---|
| 406 | | "Splits a rectangle of integer size WIDTH x HEIGHT into almost equal |
|---|
| 407 | | parts that have again integer size. If the initial rectangle does not |
|---|
| 408 | | have an extreme aspect ratio, the number of the resulting rectangles |
|---|
| 409 | | will be (sqr divisor)." |
|---|
| 410 | | ;; extreme aspect ratios are not implemented yet |
|---|
| 411 | | (flet ((divide-almost-equally (x) |
|---|
| 412 | | (multiple-value-bind (quotient remainder) |
|---|
| 413 | | (floor x divisor) |
|---|
| 414 | | (loop for i from 0 below divisor |
|---|
| 415 | | if (zerop i) |
|---|
| 416 | | collect (+ quotient remainder) |
|---|
| 417 | | else |
|---|
| 418 | | collect quotient)))) |
|---|
| 419 | | (list (divide-almost-equally width) |
|---|
| 420 | | (divide-almost-equally height)))) |
|---|
| 421 | | |
|---|
| 422 | | (defun map-children-rects (function left top width-heights depth) |
|---|
| 423 | | "Calls FUNCTION with (x y width height depth) for each of the |
|---|
| 424 | | sub-rectangles specified by the start point LEFT, TOP and |
|---|
| 425 | | WIDTH-HEIGHTS of the sub-rectangles. Collects the results into an |
|---|
| 426 | | array of dimensions corresponding to WIDTH-HEIGHTS." |
|---|
| 427 | | (let (results) |
|---|
| 428 | | (destructuring-bind (widths heights) |
|---|
| 429 | | width-heights |
|---|
| 430 | | (dolist (w widths (nreverse results)) |
|---|
| 431 | | (let ((safe-top top)) ; pretty ugly, sorry |
|---|
| 432 | | (dolist (h heights) |
|---|
| 433 | | (push (funcall function left safe-top w h depth) results) |
|---|
| 434 | | (incf safe-top h))) |
|---|
| 435 | | (incf left w))))) |
|---|
| 436 | | |
|---|
| 437 | | (defun make-image-tree (source-image geo-location &key |
|---|
| 438 | | (output-images-size 256)) |
|---|
| 439 | | "Constructs an image-tree with the given SOURCE-IMAGE. The root |
|---|
| 440 | | IMAGE-TREE-NODE will be at GEO-LOCATION. All images will be scaled to |
|---|
| 441 | | OUTPUT-IMAGES-SIZE." |
|---|
| 442 | | (destructuring-bind (geo-x geo-y geo-width geo-height) geo-location |
|---|
| 443 | | (let* ((source-image-width (cl-gd:image-width source-image)) |
|---|
| 444 | | (source-image-height (cl-gd:image-height source-image)) |
|---|
| 445 | | (scaler-x (/ source-image-width geo-width)) |
|---|
| 446 | | (scaler-y (/ source-image-height geo-height)) |
|---|
| 447 | | (classes '(image-tree . #1=(image-tree-node . #1#)))) |
|---|
| 448 | | (labels ((image-point2geo-point (x y) |
|---|
| 449 | | (list (+ (/ x scaler-x) geo-x) |
|---|
| 450 | | (+ (/ y scaler-y) geo-y))) |
|---|
| 451 | | (image-rect2geo-rect (rect) |
|---|
| 452 | | (destructuring-bind (x y width height) |
|---|
| 453 | | rect |
|---|
| 454 | | (let ((x2 (+ x width)) |
|---|
| 455 | | (y2 (+ y height))) |
|---|
| 456 | | (destructuring-bind (geo-x geo-y) |
|---|
| 457 | | (image-point2geo-point x y) |
|---|
| 458 | | (destructuring-bind (geo-x2 geo-y2) |
|---|
| 459 | | (image-point2geo-point x2 y2) |
|---|
| 460 | | (list geo-x geo-y (- geo-x2 geo-x) (- geo-y2 geo-y))))))) |
|---|
| 461 | | (image-small-enough (image-width image-height) |
|---|
| 462 | | (and (<= image-width output-images-size) |
|---|
| 463 | | (<= image-height output-images-size))) |
|---|
| 464 | | (%make-image-tree (image-x image-y image-width image-height depth) |
|---|
| 465 | | (let ((class (pop classes)) |
|---|
| 466 | | (children (unless (image-small-enough image-width image-height) |
|---|
| 467 | | (sort |
|---|
| 468 | | (map-children-rects #'%make-image-tree |
|---|
| 469 | | image-x image-y |
|---|
| 470 | | (children-sizes image-width image-height) |
|---|
| 471 | | (1+ depth)) |
|---|
| 472 | | #'image-tree-node-less)))) |
|---|
| 473 | | (cl-gd:with-image (image output-images-size output-images-size t) |
|---|
| 474 | | (cl-gd:copy-image source-image image |
|---|
| 475 | | image-x image-y 0 0 |
|---|
| 476 | | image-width image-height |
|---|
| 477 | | :resample t |
|---|
| 478 | | :resize t |
|---|
| 479 | | :dest-width output-images-size |
|---|
| 480 | | :dest-height output-images-size) |
|---|
| 481 | | #+nil |
|---|
| 482 | | (cl-gd:with-default-color ((cl-gd:allocate-color 255 0 0 :image image)) |
|---|
| 483 | | ;; (cl-gd:draw-string 10 10 (format nil "~D,~D (~D x ~D)" image-x image-y image-width image-height) |
|---|
| 484 | | ;; :font :medium :image image) |
|---|
| 485 | | (cl-gd:draw-rectangle (list 10 10 (- output-images-size 10) (- output-images-size 10)) |
|---|
| 486 | | :image image)) |
|---|
| 487 | | (make-image-tree-node image |
|---|
| 488 | | :geo-rect (image-rect2geo-rect |
|---|
| 489 | | (list image-x image-y image-width image-height)) |
|---|
| 490 | | :children children |
|---|
| 491 | | :class-name class |
|---|
| 492 | | :depth depth))))) |
|---|
| 493 | | (with-image-tree-node-counter |
|---|
| 494 | | (%make-image-tree 0 0 source-image-width source-image-height 0)))))) |
|---|
| 495 | | |
|---|
| 496 | | (defun matrix-from-list (list &key (x-key #'first) (y-key #'second)) |
|---|
| 497 | | "Converts a flat LIST to a matrix, by using X-KEY and Y-KEY to |
|---|
| 498 | | associate a position to each element of LIST. " |
|---|
| 499 | | (let* ((matrix (mapcar #'cdr (sort (group-on (sort (copy-list list) #'< :key x-key) :key y-key) #'< :key #'first))) |
|---|
| 500 | | (width (length (first matrix)))) |
|---|
| 501 | | (assert (every #'(lambda (row) (= width (length row))) matrix) |
|---|
| 502 | | nil "Cant make a proper matrix from list, cause its rows wont have the same length.") |
|---|
| 503 | | matrix)) |
|---|
| 504 | | |
|---|
| 505 | | (defun setp (list &key (test #'eql) (key #'identity)) |
|---|
| 506 | | "Checks if LIST is a set (using TEST and KEY)." |
|---|
| 507 | | (= (length list) |
|---|
| 508 | | (length (remove-duplicates list :test test :key key)))) |
|---|
| 509 | | |
|---|
| 510 | | (defun every-eql-first-p (list &key (test #'eql) (key #'identity)) |
|---|
| 511 | | "Checks if LIST only contains elements that are eql to its first |
|---|
| 512 | | element using TEST and KEY)." |
|---|
| 513 | | (let ((first-key (funcall key (first list)))) |
|---|
| 514 | | (every #'(lambda (elt) (funcall test first-key (funcall key elt))) (cdr list)))) |
|---|
| 515 | | |
|---|
| 516 | | (deftransaction combine-image-trees (image-trees) |
|---|
| 517 | | "Creates a new image-tree object that contains IMAGE-TREES as |
|---|
| 518 | | children. All necessary adoptions for the new structure are |
|---|
| 519 | | performed." |
|---|
| 520 | | (labels ((reduce-min (&rest args) |
|---|
| 521 | | (apply #'reduce #'min args)) |
|---|
| 522 | | (reduce-max (&rest args) |
|---|
| 523 | | (apply #'reduce #'max args)) |
|---|
| 524 | | (normalize-depths (node &optional (depth 0)) |
|---|
| 525 | | (setf (depth node) depth) |
|---|
| 526 | | (mapc #'(lambda (child) (normalize-depths child (1+ depth))) (children node)) |
|---|
| 527 | | node)) |
|---|
| 528 | | (assert (setp image-trees :key #'(lambda (tree) (list (geo-x tree) (geo-y tree))) :test #'equal) |
|---|
| 529 | | nil "The given image-trees have at least one duplicate with respect to their left-top position.") |
|---|
| 530 | | (assert (every-eql-first-p image-trees :key #'(lambda (tree) (list (store-image-width tree) |
|---|
| 531 | | (store-image-height tree))) |
|---|
| 532 | | :test #'equal) |
|---|
| 533 | | nil "The given image-trees must have the same width and height.") |
|---|
| 534 | | (let* ((geo-x (reduce-min image-trees :key #'geo-x)) |
|---|
| 535 | | (geo-y (reduce-min image-trees :key #'geo-y)) |
|---|
| 536 | | (geo-x-max (reduce-max image-trees :key #'(lambda (tree) (+ (geo-x tree) (geo-width tree))))) |
|---|
| 537 | | (geo-y-max (reduce-max image-trees :key #'(lambda (tree) (+ (geo-y tree) (geo-height tree))))) |
|---|
| 538 | | (first-image-tree (first image-trees)) |
|---|
| 539 | | (children-matrix (matrix-from-list image-trees :x-key #'geo-x :y-key #'geo-y)) |
|---|
| 540 | | (children-matrix-width (length (first children-matrix))) |
|---|
| 541 | | (children-matrix-height (length children-matrix))) |
|---|
| 542 | | (cl-gd:with-image (image (store-image-width first-image-tree) |
|---|
| 543 | | (store-image-height first-image-tree) |
|---|
| 544 | | t) |
|---|
| 545 | | ;; copy images |
|---|
| 546 | | (flet ((scaler-x (x) (round (/ x children-matrix-width))) |
|---|
| 547 | | (scaler-y (y) (round (/ y children-matrix-height)))) |
|---|
| 548 | | (loop with dest-y = 0 |
|---|
| 549 | | for row in children-matrix |
|---|
| 550 | | do (loop with dest-x = 0 |
|---|
| 551 | | for tree in row |
|---|
| 552 | | do (with-store-image (source-image tree) |
|---|
| 553 | | (cl-gd:copy-image source-image image |
|---|
| 554 | | 0 0 (scaler-x dest-x) (scaler-y dest-y) |
|---|
| 555 | | (store-image-width tree) (store-image-height tree) |
|---|
| 556 | | :resample t |
|---|
| 557 | | :resize t |
|---|
| 558 | | :dest-width (scaler-x (store-image-width first-image-tree)) |
|---|
| 559 | | :dest-height (scaler-y (store-image-height first-image-tree)))) |
|---|
| 560 | | do (incf dest-x (store-image-width tree))) |
|---|
| 561 | | do (incf dest-y (store-image-height (first row))))) |
|---|
| 562 | | (normalize-depths |
|---|
| 563 | | (with-image-tree-node-counter |
|---|
| 564 | | (make-image-tree-node image :geo-rect (list geo-x geo-y (- geo-x-max geo-x) (- geo-y-max geo-y)) |
|---|
| 565 | | :children (mapcar (alexandria:rcurry #'persistent-change-class 'image-tree-node) |
|---|
| 566 | | image-trees) |
|---|
| 567 | | :class-name 'image-tree))))))) |
|---|
| 568 | | |
|---|
| 569 | | |
|---|
| 570 | | ;; (cl-gd:with-image-from-file (image "/tmp/115606" :jpeg) |
|---|
| 571 | | ;; (make-image-tree image nil)) |
|---|
| 572 | | |
|---|
| 573 | | ;; (cl-gd:with-image-from-file (image "/tmp/115606" :jpeg) |
|---|
| 574 | | ;; (make-image-tree image '(0 0 10 10))) |
|---|
| 575 | | |
|---|
| 576 | | (defclass image-tree-handler (object-handler) |
|---|
| 577 | | () |
|---|
| 578 | | (:default-initargs :object-class 'image-tree-node) |
|---|
| 579 | | (:documentation "A simple html inspector for image-trees. Mainly |
|---|
| 580 | | used for debugging.")) |
|---|
| 581 | | |
|---|
| 582 | | |
|---|
| 583 | | (defun img-image-tree (object) |
|---|
| 584 | | (html |
|---|
| 585 | | ((:a :href (format nil "http://~a/image-tree/~d" (website-host) (store-object-id object))) |
|---|
| 586 | | ((:img :src (format nil "http://~a/image/~d" (website-host) (store-object-id object))))))) |
|---|
| 587 | | |
|---|
| 588 | | (defmethod handle-object ((image-tree-handler image-tree-handler) (object image-tree-node)) |
|---|
| 589 | | (with-bknr-page (:title (prin1-to-string object)) |
|---|
| 590 | | #+nil(:pre |
|---|
| 591 | | (:princ |
|---|
| 592 | | (arnesi:escape-as-html |
|---|
| 593 | | (with-output-to-string (*standard-output*) |
|---|
| 594 | | (describe object))))) |
|---|
| 595 | | (img-image-tree object) |
|---|
| 596 | | (when (parent object) |
|---|
| 597 | | (html |
|---|
| 598 | | (:p |
|---|
| 599 | | ((:a :href (format nil "http://~a/image-tree/~d" (website-host) (store-object-id (parent object)))) |
|---|
| 600 | | "go to parent")))) |
|---|
| 601 | | (:p "depth: " (:princ (depth object)) "lod-min:" (:princ (lod-min object)) "lod-max:" (:princ (lod-max object))) |
|---|
| 602 | | (:table |
|---|
| 603 | | (dolist (row (group-on (children object) :key #'geo-y :include-key nil)) |
|---|
| 604 | | (html (:tr |
|---|
| 605 | | (dolist (child row) |
|---|
| 606 | | (html (:td (img-image-tree child)))))))))) |
|---|
| 607 | | |
|---|
| 608 | | |
|---|
| 609 | | (defclass image-tree-kml-handler (object-handler) |
|---|
| 610 | | () |
|---|
| 611 | | (:default-initargs :object-class 'image-tree-node) |
|---|
| 612 | | (:documentation "Generates a kml representation of the queried |
|---|
| 613 | | image-tree-node. If the node has children, corresponding network |
|---|
| 614 | | links are created.")) |
|---|
| 615 | | |
|---|
| 616 | | (defmethod handle-object ((handler image-tree-kml-handler) (obj image-tree-node)) |
|---|
| 617 | | (hunchentoot:handle-if-modified-since (blob-timestamp obj)) |
|---|
| 618 | | (with-xml-response (:content-type "text/xml; charset=utf-8" #+nil"application/vnd.google-earth.kml+xml" |
|---|
| 619 | | :root-element "kml") |
|---|
| 620 | | (setf (hunchentoot:header-out :last-modified) |
|---|
| 621 | | (hunchentoot:rfc-1123-date (blob-timestamp obj))) |
|---|
| 622 | | (let ((lod `(:min ,(lod-min obj) :max ,(lod-max obj))) |
|---|
| 623 | | (rect (make-rectangle2 (list (geo-x obj) (geo-y obj) (geo-width obj) (geo-height obj))))) |
|---|
| 624 | | (with-element "Document" |
|---|
| 625 | | (kml-region rect lod) |
|---|
| 626 | | (kml-overlay (format nil "http://~a/image/~d" (website-host) (store-object-id obj)) |
|---|
| 627 | | rect |
|---|
| 628 | | :draw-order (depth obj) |
|---|
| 629 | | ;; :absolute 0 |
|---|
| 630 | | ) |
|---|
| 631 | | (dolist (child (children obj)) |
|---|
| 632 | | (kml-network-link (format nil "http://~a/image-tree-kml/~d" (website-host) (store-object-id child)) |
|---|
| 633 | | :rect (make-rectangle2 (list (geo-x child) (geo-y child) |
|---|
| 634 | | (geo-width child) (geo-height child))) |
|---|
| 635 | | :lod `(:min ,(lod-min child) :max ,(lod-max child)))))))) |
|---|
| 636 | | |
|---|
| 637 | | (defclass image-tree-kml-latest-handler (page-handler) |
|---|
| 638 | | () |
|---|
| 639 | | (:documentation "A convenience handler that redirects to the |
|---|
| 640 | | IMAGE-TREE-KML-HANDLER of the latest created image-tree.")) |
|---|
| 641 | | |
|---|
| 642 | | (defmethod handle ((page-handler image-tree-kml-latest-handler)) |
|---|
| 643 | | (redirect (format nil "http://~a/image-tree-kml/~d" (website-host) (store-object-id (car (last (class-instances 'image-tree))))))) |
|---|
| 644 | | |
|---|
| 645 | | ;;;; |
|---|
| 646 | | (defun image-tree-import-satellitenbild () |
|---|
| 647 | | "A simple importer for our standard image." |
|---|
| 648 | | (labels ((2x2-indices (left top) |
|---|
| 649 | | `((,left ,top)(,(1+ left) ,top)(,left ,(1+ top))(,(1+ left) ,(1+ top)))) |
|---|
| 650 | | (aref-indices (array indices) |
|---|
| 651 | | (mapcar #'(lambda (index-pair) (destructuring-bind (x y) index-pair (aref array x y))) indices))) |
|---|
| 652 | | (let ((array (make-array (list 4 4)))) |
|---|
| 653 | | (loop with *default-pathname-defaults* = (merge-pathnames #p"tiles-2700/" (user-homedir-pathname)) |
|---|
| 654 | | for name in '("sl_utm50s_01.png" |
|---|
| 655 | | "sl_utm50s_02.png" |
|---|
| 656 | | "sl_utm50s_03.png" |
|---|
| 657 | | "sl_utm50s_04.png" |
|---|
| 658 | | "sl_utm50s_05.png" |
|---|
| 659 | | "sl_utm50s_06.png" |
|---|
| 660 | | "sl_utm50s_07.png" |
|---|
| 661 | | "sl_utm50s_08.png" |
|---|
| 662 | | "sl_utm50s_09.png" |
|---|
| 663 | | "sl_utm50s_10.png" |
|---|
| 664 | | "sl_utm50s_11.png" |
|---|
| 665 | | "sl_utm50s_12.png" |
|---|
| 666 | | "sl_utm50s_13.png" |
|---|
| 667 | | "sl_utm50s_14.png" |
|---|
| 668 | | "sl_utm50s_15.png" |
|---|
| 669 | | "sl_utm50s_16.png") |
|---|
| 670 | | for i upfrom 0 |
|---|
| 671 | | for x = (mod i 4) |
|---|
| 672 | | for y = (floor i 4) |
|---|
| 673 | | do (print (list 'importing x y)) |
|---|
| 674 | | do (setf (aref array x y) |
|---|
| 675 | | (cl-gd:with-image-from-file (image (merge-pathnames name)) |
|---|
| 676 | | (make-image-tree image (list (* (mod i 4) 2700) (* (floor i 4) 2700) |
|---|
| 677 | | 2700 2700))))) |
|---|
| 678 | | (combine-image-trees |
|---|
| 679 | | (list (combine-image-trees (aref-indices array (2x2-indices 0 0))) |
|---|
| 680 | | (combine-image-trees (aref-indices array (2x2-indices 0 2))) |
|---|
| 681 | | (combine-image-trees (aref-indices array (2x2-indices 2 0))) |
|---|
| 682 | | (combine-image-trees (aref-indices array (2x2-indices 2 2)))))))) |
|---|