function imwrite(varargin)
%IMWRITE Write image to graphics file.
%   IMWRITE(A,FILENAME,FMT) writes the image A to FILENAME.
%   FILENAME is a string that specifies the name of the output
%   file, and FMT is a string the specifies the format of the
%   file.  A can be either a grayscale image (M-by-N) or a
%   truecolor image (M-by-N-by-3).
%
%   The possible values for FMT include:
%
%      'tif' or 'tiff' Tagged Image File Format (TIFF)
%      'jpg' or 'jpeg' Joint Photographic Experts Group (JPEG)
%      'bmp'           Windows Bitmap (BMP)
%      'png'           Portable Network Graphics (PNG)
%      'hdf'           Hierarchical Data Format (HDF)
%      'pcx'           Windows Paintbrush (PCX)
%      'xwd'           X Window Dump (XWD)
%
%   IMWRITE(X,MAP,FILENAME,FMT) writes the indexed image in X,
%   and its associated colormap MAP, to FILENAME.  If X is of
%   class uint8 or uint16, IMWRITE writes the actual values in
%   the array to the file.  If X is of class double, IMWRITE
%   offsets the values in the array before writing, using
%   uint8(X-1).  MAP must be a valid MATLAB colormap.  Note
%   that most image file formats do not support colormaps with
%   more than 256 entries.
%
%   IMWRITE(...,FILENAME) writes the image to FILENAME,
%   inferring the format to use from the filename's extension.
%   The extension must be one of the legal values for FMT.
%
%   IMWRITE(...,PARAM1,VAL1,PARAM2,VAL2,...) specifies
%   parameters that control various characteristics of the
%   output file. Parameters are currently supported for HDF,
%   JPEG, TIFF, and PNG files.
%
%   Data types
%   ----------
%   Most of the supported image file formats store uint8
%   data.  PNG and TIFF additionally support uint16 data.  For
%   grayscale and RGB images, if the data array is double, the
%   assumed dynamic range is [0,1].  The data array is
%   automatically scaled by 255 before being written out as
%   uint8.  If the data array is uint8 or uint16 (PNG and TIFF
%   only), then it is written out without scaling as uint8 or
%   uint16, respectively.  NOTE: If a logical double or uint8
%   is written to a PNG or TIFF file, it is assumed to be a
%   binary image and will be written with a bitdepth of 1.
%
%   For indexed images, if the index array is double, then the
%   indices are first converted to zero-based indices by
%   subtracting 1 from each element, and then they are written
%   out as uint8.  If the index array is uint8 or uint16 (PNG
%   and TIFF only), then it is written out without
%   modification as uint8 or uint16, respectively.
%
%   When writing PNG files, you can override this behavior
%   with the 'BitDepth' parameter; see below for details.
%
%   HDF-specific parameters
%   -----------------------
%   'Compression'  One of these strings: 'none' (the default), 
%                  'rle' (only valid for grayscale and indexed
%                  images), 'jpeg' (only valid for grayscale
%                  and RGB images)
%
%   'Quality'      A number between 0 and 100; parameter applies
%                  only if 'Compression' is 'jpeg'; higher
%                  numbers mean quality is better (less image
%                  degradation due to compression), but the
%                  resulting file size is larger 
%
%   'WriteMode'    One of these strings: 'overwrite' (the
%                  default) or 'append'
%
%   JPEG parameters and values include:
%
%   'Quality'      A number between 0 and 100; higher numbers
%                  mean quality is better (less image degradation
%                  due to compression), but the resulting file
%                  size is larger
%
%   TIFF-specific parameters
%   ------------------------
%   'Compression'  One of these strings: 'none', 'packbits'
%                  (default for nonbinary images), 'ccitt'
%                  (default for binary images); 'ccitt' is valid
%                  for binary images only
%
%   'Description'  Any string; fills in the ImageDescription
%                  field returned by IMFINFO
%
%   'Resolution'   A two-element vector containing the
%                  XResolution and YResolution, or a scalar
%                  indicating both resolutions; the default value
%                  is 72
%
%   PNG-specific parameters
%   -----------------------
%   'Author'       A string
%
%   'Description'  A string
%
%   'Copyright'    A string
%
%   'CreationTime' A string
%
%   'Software'     A string
%
%   'Disclaimer'   A string
%
%   'Warning'      A string
%
%   'Source'       A string
%
%   'Comment'      A string
%
%   'InterlaceType' Either 'none' or 'adam7'
%
%   'BitDepth'     A scalar value indicating desired bitdepth;
%                  for grayscale images this can be 1, 2, 4,
%                  8, or 16; for grayscale images with an
%                  alpha channel this can be 8 or 16; for
%                  indexed images this can be 1, 2, 4, or 8;
%                  for truecolor images with or without an
%                  alpha channel this can be 8 or 16
%
%   'Transparency' This value is used to indicate transparency
%                  information when no alpha channel is used.
%                  
%                  For indexed images: a Q-element vector in
%                    the range [0,1]; Q is no larger than the
%                    colormap length; each value indicates the
%                    transparency associated with the
%                    corresponding colormap entry
%                  For grayscale images: a scalar in the range
%                    [0,1]; the value indicates the grayscale
%                    color to be considered transparent
%                  For truecolor images: a 3-element vector in
%                    the range [0,1]; the value indicates the
%                    truecolor color to be considered
%                    transparent
%
%                  You cannot specify 'Transparency' and
%                  'Alpha' at the same time.
%
%   'Background'   The value specifies background color to be
%                  used when compositing transparent pixels.
%
%                  For indexed images: an integer in the range
%                    [1,P], where P is the colormap length
%                  For grayscale images: a scalar in the range
%                    [0,1]
%                  For truecolor images: a 3-element vector in
%                    the range [0,1]
%
%   'Gamma'        A nonnegative scalar indicating the file
%                  gamma
%
%   'Chromaticities' An 8-element vector [wx wy rx ry gx gy bx
%                  by] that specifies the reference white
%                  point and the primary chromaticities 
%
%   'XResolution'  A scalar indicating the number of
%                  pixels/unit in the horizontal direction
%
%   'YResolution'  A scalar indicating the number of
%                  pixels/unit in the vertical direction
%
%   'ResolutionUnit' Either 'unknown' or 'meter'
%
%   'Alpha'        A matrix specifying the transparency of
%                  each pixel individually; the row and column
%                  dimensions must be the same as the data
%                  array; may be uint8, uint16, or double, in
%                  which case the values should be in the
%                  range [0,1]
%
%   'SignificantBits' A scalar or vector indicating how many
%                  bits in the data array should be regarded
%                  as significant; values must be in the range
%                  [1,bitdepth]
%
%                  For indexed images: a 3-element vector
%                  For grayscale images: a scalar
%                  For grayscale images with an alpha channel:
%                    a 2-element vector
%                  For truecolor images: a 3-element vector
%                  For truecolor images with an alpha channel:
%                    a 4-element vector
%
%   In addition to these PNG parameters, you can use any
%   parameter name that satisfies the PNG specification for
%   keywords: only printable characters, 80 characters or
%   fewer, and no leading or trailing spaces.  The value
%   corresponding to these user-specified parameters must be a
%   string that contains no control characters except for
%   linefeed.
%
%   Table: summary of supported image types
%   ---------------------------------------
%   BMP   8-bit and 24-bit uncompressed images
%
%   TIFF  Baseline TIFF images, including 1-bit, 8-bit, and
%         24-bit uncompressed images; 1-bit, 8-bit, and 24-bit
%         images with packbits compression; 1-bit images with
%         CCITT compression
%
%   JPEG  Baseline JPEG images
%
%   PNG   1-bit, 2-bit, 4-bit, 8-bit, and 16-bit grayscale
%         images; 8-bit and 16-bit grayscale images with alpha
%         channels; 1-bit, 2-bit, 4-bit, and 8-bit indexed
%         images; 24-bit and 48-bit truecolor images; 24-bit
%         and 48-bit truecolor images with alpha channels
%
%   HDF   8-bit raster image datasets, with or without associated
%         colormap; 24-bit raster image datasets; uncompressed or
%         with RLE or JPEG compression
%
%   PCX   8-bit images
%
%   XWD   8-bit ZPixmaps
%
%   See also IMFINFO, IMREAD, FWRITE.

%   Steven L. Eddins, June 1996
%   Copyright (c) 1984-98 by The MathWorks, Inc.
%   $Revision: 1.11 $  $Date: 1998/05/08 18:37:39 $

[data, map, filename, format, paramPairs, msg] = parse_inputs(varargin{:});

if (~isempty(msg))
    error(msg);
end

if (isempty(format))
    format = GetFormatFromFilename(filename);
    if (isempty(format))
        error('Unable to determine the file format from the filename');
    end
end

switch format
case 'hdf'
    writehdf(data, map, filename, paramPairs{:});
    
case 'jpg'
    writejpg(data, map, filename, paramPairs{:});
    
case 'tif'
    writetif(data, map, filename, paramPairs{:});
    
case 'xwd'
    writexwd(data, map, filename, paramPairs{:});
    
case 'pcx'
    writepcx(data, map, filename, paramPairs{:});
    
case 'bmp'
    writebmp(data, map, filename, paramPairs{:});
    
case 'png'
    writepng(data, map, filename, paramPairs{:});

otherwise
    error(sprintf('Unrecognized or unsupported format "%s"', format));
    
end


%%%
%%% Function parse_inputs
%%%
function [data, map, filename, format, paramPairs, msg] = ...
        parse_inputs(varargin)

data = [];
map = [];
filename = '';
format = '';
paramPairs = {};
msg = '';

if (nargin < 2)
    msg = 'Too few input arguments';
    return;
end

firstString = [];
for k = 1:length(varargin)
    if (isstr(varargin{k}))
        firstString = k;
        break;
    end
end

if (isempty(firstString))
    msg = 'Invalid input arguments: missing filename';
    return;
end

switch firstString
case 1
    msg = 'Invalid input arguments: first argument should not be a string';
    return;
    
case 2
    % imwrite(data, filename, ...)
    data = varargin{1};
    filename = varargin{2};
    
case 3
    % imwrite(data, map, filename, ...)
    data = varargin{1};
    map = varargin{2};
    filename = varargin{3};
    if (size(map,2) ~= 3)
        msg = 'Invalid colormap';
        return;
    end

otherwise
    msg = 'Invalid input arguments: missing filename';
    return;
end

if (length(varargin) > firstString)
    % There are additional arguments after the filename.
    if (~isstr(varargin{firstString+1}))
        msg = 'Invalid input arguments';
        return;
    end
    
    % Is the argument after the filename a format specifier?
    % Allow mixed case; 'jpeg' for 'jpg'; 'tiff' for 'tif'.
    formatStrings = ['bmp'
        'xwd'
        'pcx'
        'mat'
        'hdf'
        'tif'
        'jpg'
        'png'];
    arg = lower(varargin{firstString + 1});
    if (strcmp(arg, 'tiff'))
        arg = 'tif';
    elseif (strcmp(arg, 'jpeg'))
        arg = 'jpg';
    end
    
    idx = strmatch(arg, formatStrings, 'exact');
    if (~isempty(idx))
        % imwrite(..., filename, fmt, ...)
        format = deblank(formatStrings(idx,:));
        paramPairs = varargin(firstString+2:end);
        
    else
        % imwrite(..., filename, prop1, val1, prop2, val2, ...)
        paramPairs = varargin(firstString+1:end);
    end
    
end

% Do some validity checking on param-value pairs
if (rem(length(paramPairs),2) ~= 0)
    msg = 'Invalid input syntax';
    return;
end

for k = 1:2:length(paramPairs)
    if (~isstr(paramPairs{k}))
        msg = 'Parameter names must be strings';
        return;
    end
end
        
function format = GetFormatFromFilename(filename)

idx = find(filename == '.');
if (~isempty(idx))
    extension = lower(filename(idx(end)+1:end));
    switch extension
    case 'bmp'
        format = 'bmp';
        
    case 'xwd'
        format = 'xwd';
        
    case 'pcx'
        format = 'pcx';
        
    case 'jpeg'
        format = 'jpg';
        
    case 'jpg'
        format = 'jpg';
        
    case 'tiff'
        format = 'tif';
        
    case 'tif'
        format = 'tif';
        
    case 'hdf'
        format = 'hdf';
        
    case 'png'
        format = 'png';
        
    otherwise
        format = '';
        
    end
end
