;;; xlib-xpm.el --- XPM library for Xlib.

;; Copyright (C) 2003-2005 by XWEM Org.

;; Author: Zajcev Evgeny <zevlg@yandex.ru>
;; Created: Fri Nov 28 01:28:18 MSK 2003
;; Keywords: xlib, xwem
;; X-CVS: $Id: xlib-xpm.el,v 1.7 2005/04/04 19:55:30 lg Exp $

;; This file is part of XWEM.

;; XWEM is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; XWEM is distributed in the hope that it will be useful, but WITHOUT
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
;; License for more details.

;; You should have received a copy of the GNU General Public License
;; along with XEmacs; see the file COPYING.  If not, write to the Free
;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
;; 02111-1307, USA.

;;; Synched up with: Not in FSF

;;; Commentary:

;; Almost everything is hack here.

;;; TODO:
;;
;;   * Rewrite.

;;; Code:

(require 'xlib-img)

(defvar X:xpm-color-symbols nil
  "Same as `xpm-color-symbols', but for xlib.")


(defun X:xpm-num-colors ()
  "Return number of colors in xpm."
  (save-excursion
    (goto-char (point-min))
    (if (re-search-forward 
	 "\"\\s-*\\([0-9]+\\)\\s-+\\([0-9]+\\)\\s-+\\([0-9]+\\)\\s-+\\([0-9]+\\)\\s-*"
	 (point-max) t)
	(string-to-int (match-string 3))
      (error "Unable to parse xpm information"))))

(defun X:xpm-goto-color-def (def)
  "Move to color DEF in the xpm header."
  (goto-char (point-min))
  (re-search-forward "[ \t]*\"")
  (forward-line 1)
  (re-search-forward "[ \t]*\"")
  (forward-line def))

(defun X:xpm-goto-body-line (line &optional num-colors)
  "Move to LINE lines down from the start of the body of an xpm."
  (X:xpm-goto-color-def (or num-colors (X:xpm-num-colors)))
  (forward-line line))

(defun X:xpm-chars-per-pixel ()
  "Return number of chars per pixel."
  (save-excursion
    (goto-char (point-min))
    (if (re-search-forward 
         "\"\\s-*\\([0-9]+\\)\\s-+\\([0-9]+\\)\\s-+\\([0-9]+\\)\\s-+\\([0-9]+\\)\\s-*"
         (point-max) t)
        (string-to-int (match-string 4))
      (error "Unable to parse xpm."))))

(defun X:xpm-get-symcolor (symc-name &optional tag-set)
  "Get SYMC-NAME color from `X:xpm-color-symbols' list.
TAGS-SET is a list of tags, directly passed to `specifier-spec-list'."
  (let ((xcs X:xpm-color-symbols))
    (while (and xcs (not (string= (caar xcs) symc-name)))
      (setq xcs (cdr xcs)))
    (when xcs
      (setq xcs (cadr xcs))
      (cond ((stringp xcs) xcs)
            ((specifierp xcs)
             (let ((sspec (specifier-spec-list xcs nil tag-set t)))
               (cdr (car (cdr (car sspec))))))
            (t (error "Invalid element in `X:xpm-color-symbols'"))))))

(defun X:xpm-parse-color (chars-per-pixel &optional tag-set)
  "Parse xpm color string from current line and set the color.
CHARS-PER-PIXEL specifies color depth.
TAG-SET specifies tag-set to use in order to resolve symbolic color name."
  (save-excursion
    (beginning-of-line)
    (if (re-search-forward
         ;; Generate a regexp on the fly
         (concat "\"\\(" (make-string chars-per-pixel ?.) "\\)" ; chars
                 "\\(\\s-+[sm]\\s-+\\(\\S-*\\)\\)*" ; s and m classes
                 "\\s-+\\([c]\\)"       ; c class
                 "\\s-+\\([^ \t\"]+\\)")
         (point-at-eol) t)
        (list (match-string 1)
              (or (and (match-string 3) (X:xpm-get-symcolor (match-string 3) tag-set))
                  (match-string 5)))
      (error "Unable to parse color"))))

;;; Shapes
(defun X:xpm-extract-shape-colors (xdpy &optional tag-set)
  "Extract colors which marked as None."
  (let ((xpm-chars-per-pixel (X:xpm-chars-per-pixel))
        (xpm-num-colors (X:xpm-num-colors))
        (co 0)
	pco nonecols)

    ;; extract colors which need to mask
    (X:xpm-goto-color-def 0)
    (while (< co xpm-num-colors)
      (setq pco (X:xpm-parse-color xpm-chars-per-pixel tag-set))
      (when (string= "none" (downcase (cadr pco)))
	(setq nonecols (cons (car pco) nonecols)))

      (setq co (1+ co))
      (forward-line 1))
    nonecols))
  
(defun X:xpm-bit-vector-to-string (bitv)
  "Convert bit-vector BITV to string."
  (let ((off 0)
	(coff 0)
	(idx 0)
	(str (make-string (+ (/ (length bitv) 8)
			     (if (not (zerop (% (length bitv) 8))) 1 0)) 0)))

    (setq idx 0)
    (while (< idx (length bitv))
      (setq off (/ idx 8)
	    coff (% idx 8))
      (aset str off
	    (logior (aref str off) (lsh (* (aref bitv idx) #x80) (- coff))))

      (setq idx (1+ idx)))
    str))

(defun X:xpm-parse-shape-body-line (nonec width left-pad xpad)
  "Parse current line to extract bits using None colors list NONEC."
  (let ((bitv (make-vector (+ width (- xpad (% width xpad))) 0))
	(bidx 0)
	pix col)

    (forward-char)
    ;; XXX
    (flet ((xpm-calc-off (idx)		; XXX offset in BITV calculator
			 (1- (if (< idx (- 8 left-pad))
				 (- (- 8 left-pad) idx)

			       (setq idx (+ idx left-pad))
			       (+ (* 8 (/ idx 8)) (- 8 (% idx 8)))))))

      (while (< bidx width)
	(setq pix (buffer-substring (point) (+ (point) 1))
	      col (member pix nonec))
	(aset bitv (xpm-calc-off bidx) (if col 0 1))
	(forward-char)
	(setq bidx (1+ bidx))))

    (X:xpm-bit-vector-to-string bitv)))

(defun X:xpm-make-shape (xdpy &optional tag-set)
  "Extract shape bits.
Return data for `X-XYPixmap' format."
  (let ((togo 0)
	ximg shape shape-index nonec height width
	left-pad)
    (goto-char (point-min))
    (save-excursion
      (when (re-search-forward "\"\\s-*\\([0-9]+\\)\\s-+\\([0-9]+\\)\\s-+\\([0-9]+\\)\\s-+\\([0-9]+\\)\\s-*"
			       (point-max) t)
	(setq width (string-to-int (match-string 1)))
	(setq height (string-to-int (match-string 2)))))

    (setq left-pad (% width 8))

    (save-excursion 
      (setq nonec (X:xpm-extract-shape-colors xdpy)))

    (X:xpm-goto-body-line 0)
    ;; XXX Check for addition comment line
    (when (looking-at "^/\\*")
      (setq togo 1))

    (setq shape (make-vector height nil))
    (setq shape-index 0)

    (while (< shape-index height)
      (X:xpm-goto-body-line (+ shape-index togo))
      (aset shape shape-index
	    (X:xpm-parse-shape-body-line nonec width left-pad (X-Dpy-bitmap-scanline-pad xdpy)))

      (setq shape-index (1+ shape-index)))

    ;; XXX
    (setq ximg (XCreateImage xdpy nil 1 X-XYBitmap left-pad shape
			     width height (X-Dpy-bitmap-scanline-pad xdpy) 1))
    ximg))


;;; Icons
(defun X:xpm-extract-colors (xdpy &optional tag-set)
  "Return color list."
  (let ((cmap (XDefaultColormap xdpy))
        (xpm-num-colors (X:xpm-num-colors))
        (xpm-chars-per-pixel (X:xpm-chars-per-pixel))
	(co 0)
	pco prgb
	colors)

    (X:xpm-goto-color-def 0)
    (while (< co xpm-num-colors)
      (setq pco (X:xpm-parse-color xpm-chars-per-pixel tag-set))
      (setq prgb (color-instance-rgb-components
		  (make-color-instance (if (string= "none" (downcase (cadr pco)))
					   "white"
					 (cadr pco)))))
      (setq colors (cons
		    (cons (car pco)
			  (XAllocColor xdpy cmap
				       (make-X-Color :dpy xdpy :id (X-Dpy-get-id xdpy)
						     :red (nth 0 prgb)
						     :green (nth 1 prgb)
						     :blue (nth 2 prgb))))
		    colors))
      (setq co (1+ co))
      (forward-line 1))
    colors))

(defun X:xpm-parse-body-line (cols)
  (let (pix col rlst)
    (while (not (eolp))
      (setq pix (buffer-substring (point) (+ (point) 1))
	    col (assoc pix cols))
      (when (X-Color-p (cdr col))
	(setq rlst (cons (X-Color-id (cdr col)) rlst)))
      (forward-char 1))
    (vconcat (nreverse rlst))))

(defun X:xpm-make-img (xdpy &optional tag-set)
  "Create X-Image using current buffer."
  (let ((depth (XDefaultDepth xdpy))
        (xpm-num-colors (X:xpm-num-colors))
	(togo 0)
	xpm xpm-index height width cols
	data ximg)
    (goto-char (point-min))
    (save-excursion
      (when (re-search-forward "\"\\s-*\\([0-9]+\\)\\s-+\\([0-9]+\\)\\s-+\\([0-9]+\\)\\s-+\\([0-9]+\\)\\s-*"
			       (point-max) t)
	(setq width (string-to-int (match-string 1)))
	(setq height (string-to-int (match-string 2)))))

    (save-excursion 
      (setq cols (X:xpm-extract-colors xdpy tag-set)))

    (X:xpm-goto-body-line 0 xpm-num-colors)
    ;; Check for additional comment
    (when (looking-at "^/\\*")
      (setq togo 1))

    (setq xpm (make-vector height nil))
    (setq xpm-index 0)

    (while (< xpm-index height)
      (X:xpm-goto-body-line (+ xpm-index togo) xpm-num-colors)
      (aset xpm xpm-index
	    (X:xpm-parse-body-line cols))

      (setq xpm-index (1+ xpm-index)))

    (setq data (mapvector #'(lambda (row)
                              (X-formatpad xdpy depth
                                           (apply 'concat
                                                  (mapcar #'(lambda (col)
                                                              (X-formatint xdpy depth col))
                                                          row))))
			  xpm))
    ;; XXX
    (setq ximg (XCreateImage xdpy nil depth X-ZPixmap 0 data
			     width height (X-Dpy-bitmap-scanline-pad xdpy) 1))
    ximg))
  
(defun X:xpm-img-from-data (xdpy data &optional shape tag-set)
  (with-temp-buffer
    (insert data)
    (if shape
	(X:xpm-make-shape xdpy tag-set)
      (X:xpm-make-img xdpy tag-set))))

(defun X:xpm-img-from-file (xdpy file &optional shape tag-set)
  (with-temp-buffer
    (insert-file-contents-literally file)
    (if shape
	(X:xpm-make-shape xdpy tag-set)
      (X:xpm-make-img xdpy tag-set))))

;; Pixmap manipulations
(defun X:xpm-make-pixmap-from-ximg (xdpy d ximg)
  "On display XDPY and drawable D, create X-Pixmap using X-Image XIMG."
  (let (pixmap gc)
    (setq pixmap (XCreatePixmap xdpy (make-X-Pixmap :dpy xdpy :id (X-Dpy-get-id xdpy))
				d (X-Image-depth ximg) (X-Image-width ximg) (X-Image-height ximg)))

    (setq gc (XCreateGC xdpy pixmap
			(make-X-Gc :dpy xdpy :id (X-Dpy-get-id xdpy)
				   :foreground 1.0
				   :background 0.0)))

    (XImagePut xdpy gc pixmap 0 0 ximg)
    (XFreeGC xdpy gc)

    ;; Store initial X-Image XIMG in pixmap's properties list
    (X-Pixmap-put-prop pixmap 'ximg ximg)
    pixmap))

(defun X:xpm-pixmap-from-data (xdpy d data &optional shape tag-set)
  "On display XDPY and drawable D create X-Pixmap from DATA."
  (X:xpm-make-pixmap-from-ximg xdpy d (X:xpm-img-from-data xdpy data shape tag-set)))
  
(defun X:xpm-pixmap-from-file (xdpy d file &optional shape tag-set)
  "On display XDPY and drawable D create X-Pixmap from FILE."
  (X:xpm-make-pixmap-from-ximg xdpy d (X:xpm-img-from-file xdpy file shape tag-set)))


;;; Scaling:
;;
;; * bicubic interpolation
(defsubst X:xpm-func-P (x)
  (if (> x 0) (float x) 0.0))

(defsubst X:xpm-func-R (x)
  (/ (+ (X:xpm-func-P (expt (+ x 2) 3)) (- (* 4  (X:xpm-func-P (expt (+ x 1) 3))))
	(* 6.0 (X:xpm-func-P (expt x 3))) (- (* 4 (X:xpm-func-P (expt (- x 1) 3)))))
     6))

(defun X:xpm-get-color-component-at (xdpy img x y component)
  (when (< x 0)
    (setq x 0))
  (when (< y 0)
    (setq y 0))
  (when (>= y (length img))
    (setq y (1- (length img))))
  (when (>= x (length (nth 0 img)))
    (setq x (1- (length (nth 0 img)))))

  (let ((col (aref (nth y img) x)))
    (nth (cond ((eq component 'red) 0)
	       ((eq component 'green) 1)
	       ((eq component 'blue) 2)
	       (t 0))
	 (caar (last (XQueryColors xdpy (XDefaultColormap xdpy) (list col)))))))

(defun X:xpm-func-F (xdpy img factors i j)
  ;; TODO: calculate dx, dy
  (let* ((x (* i (car factors)))
	 (y (* j (cdr factors)))
	 (dx (- x i))
	 (dy (- y i)))

    (vconcat (mapcar (lambda (comp)
		       (round (apply '+
				     (mapcar (lambda (m)
					       (apply '+
						      (mapcar (lambda (n)
								(* (X:xpm-get-color-component-at xdpy img (+ i m) (+ j n) comp)
								   (X:xpm-func-R (- dx m))
								   (X:xpm-func-R (- dy n))))
							      '(-1 0 1 2))))
					     '(-1 0 1 2)))))
		     '(red green blue)))
    ))
     

(provide 'xlib-xpm)

;;; xlib-xpm.el ends here
