Mercurial > hg > audiostuff
diff spandsp-0.0.6pre17/src/t4_rx.c @ 4:26cd8f1ef0b1
import spandsp-0.0.6pre17
author | Peter Meerwald <pmeerw@cosy.sbg.ac.at> |
---|---|
date | Fri, 25 Jun 2010 15:50:58 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/spandsp-0.0.6pre17/src/t4_rx.c Fri Jun 25 15:50:58 2010 +0200 @@ -0,0 +1,1227 @@ +//#define T4_STATE_DEBUGGING +/* + * SpanDSP - a series of DSP components for telephony + * + * t4_rx.c - ITU T.4 FAX receive processing + * + * Written by Steve Underwood <steveu@coppice.org> + * + * Copyright (C) 2003, 2007 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t4_rx.c,v 1.12.2.8 2009/12/21 17:18:39 steveu Exp $ + */ + +/* + * Much of this file is based on the T.4 and T.6 support in libtiff, which requires + * the following notice in any derived source code: + * + * Copyright (c) 1990-1997 Sam Leffler + * Copyright (c) 1991-1997 Silicon Graphics, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that (i) the above copyright notices and this permission notice appear in + * all copies of the software and related documentation, and (ii) the names of + * Sam Leffler and Silicon Graphics may not be used in any advertising or + * publicity relating to the software without the specific, prior written + * permission of Sam Leffler and Silicon Graphics. + * + * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR + * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, + * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF + * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + * + * Decoder support is derived from code in Frank Cringle's viewfax program; + * Copyright (C) 1990, 1995 Frank D. Cringle. + */ + +/*! \file */ + +#if defined(HAVE_CONFIG_H) +#include "config.h" +#endif + +#include <stdlib.h> +#include <inttypes.h> +#include <limits.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <time.h> +#include <memory.h> +#include <string.h> +#if defined(HAVE_TGMATH_H) +#include <tgmath.h> +#endif +#if defined(HAVE_MATH_H) +#include <math.h> +#endif +#include "floating_fudge.h" +#include <tiffio.h> + +#include "spandsp/telephony.h" +#include "spandsp/logging.h" +#include "spandsp/bit_operations.h" +#include "spandsp/async.h" +#include "spandsp/t4_rx.h" +#include "spandsp/t4_tx.h" +#include "spandsp/version.h" + +#include "spandsp/private/logging.h" +#include "spandsp/private/t4_rx.h" +#include "spandsp/private/t4_tx.h" + +/*! The number of centimetres in one inch */ +#define CM_PER_INCH 2.54f + +/*! The number of EOLs to expect at the end of a T.4 page */ +#define EOLS_TO_END_ANY_RX_PAGE 6 +/*! The number of EOLs to check at the end of a T.4 page */ +#define EOLS_TO_END_T4_RX_PAGE 5 +/*! The number of EOLs to check at the end of a T.6 page */ +#define EOLS_TO_END_T6_RX_PAGE 2 + +#if defined(T4_STATE_DEBUGGING) +static void STATE_TRACE(const char *format, ...) +{ + va_list arg_ptr; + + va_start(arg_ptr, format); + vprintf(format, arg_ptr); + va_end(arg_ptr); +} +/*- End of function --------------------------------------------------------*/ +#else +#define STATE_TRACE(...) /**/ +#endif + +/*! T.4 run length table entry */ +typedef struct +{ + /*! Length of T.4 code, in bits */ + uint16_t length; + /*! T.4 code */ + uint16_t code; + /*! Run length, in bits */ + int16_t run_length; +} t4_run_table_entry_t; + +#include "t4_t6_decode_states.h" + +#if defined(HAVE_LIBTIFF) +static int set_tiff_directory_info(t4_state_t *s) +{ + time_t now; + struct tm *tm; + char buf[256 + 1]; + uint16_t resunit; + float x_resolution; + float y_resolution; + t4_tiff_state_t *t; + + t = &s->tiff; + /* Prepare the directory entry fully before writing the image, or libtiff complains */ + TIFFSetField(t->tiff_file, TIFFTAG_COMPRESSION, t->output_compression); + if (t->output_compression == COMPRESSION_CCITT_T4) + { + TIFFSetField(t->tiff_file, TIFFTAG_T4OPTIONS, t->output_t4_options); + TIFFSetField(t->tiff_file, TIFFTAG_FAXMODE, FAXMODE_CLASSF); + } + TIFFSetField(t->tiff_file, TIFFTAG_IMAGEWIDTH, s->image_width); + TIFFSetField(t->tiff_file, TIFFTAG_BITSPERSAMPLE, 1); + TIFFSetField(t->tiff_file, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(t->tiff_file, TIFFTAG_SAMPLESPERPIXEL, 1); + if (t->output_compression == COMPRESSION_CCITT_T4 + || + t->output_compression == COMPRESSION_CCITT_T6) + { + TIFFSetField(t->tiff_file, TIFFTAG_ROWSPERSTRIP, -1L); + } + else + { + TIFFSetField(t->tiff_file, + TIFFTAG_ROWSPERSTRIP, + TIFFDefaultStripSize(t->tiff_file, 0)); + } + TIFFSetField(t->tiff_file, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(t->tiff_file, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE); + TIFFSetField(t->tiff_file, TIFFTAG_FILLORDER, FILLORDER_LSB2MSB); + + x_resolution = s->x_resolution/100.0f; + y_resolution = s->y_resolution/100.0f; + /* Metric seems the sane thing to use in the 21st century, but a lot of lousy software + gets FAX resolutions wrong, and more get it wrong using metric than using inches. */ +#if 0 + TIFFSetField(t->tiff_file, TIFFTAG_XRESOLUTION, x_resolution); + TIFFSetField(t->tiff_file, TIFFTAG_YRESOLUTION, y_resolution); + resunit = RESUNIT_CENTIMETER; + TIFFSetField(t->tiff_file, TIFFTAG_RESOLUTIONUNIT, resunit); +#else + TIFFSetField(t->tiff_file, TIFFTAG_XRESOLUTION, floorf(x_resolution*CM_PER_INCH + 0.5f)); + TIFFSetField(t->tiff_file, TIFFTAG_YRESOLUTION, floorf(y_resolution*CM_PER_INCH + 0.5f)); + resunit = RESUNIT_INCH; + TIFFSetField(t->tiff_file, TIFFTAG_RESOLUTIONUNIT, resunit); +#endif + /* TODO: add the version of spandsp */ + TIFFSetField(t->tiff_file, TIFFTAG_SOFTWARE, "Spandsp " SPANDSP_RELEASE_DATETIME_STRING); + if (gethostname(buf, sizeof(buf)) == 0) + TIFFSetField(t->tiff_file, TIFFTAG_HOSTCOMPUTER, buf); + +#if defined(TIFFTAG_FAXDCS) + if (t->dcs) + TIFFSetField(t->tiff_file, TIFFTAG_FAXDCS, t->dcs); +#endif + if (t->sub_address) + TIFFSetField(t->tiff_file, TIFFTAG_FAXSUBADDRESS, t->sub_address); + if (t->far_ident) + TIFFSetField(t->tiff_file, TIFFTAG_IMAGEDESCRIPTION, t->far_ident); + if (t->vendor) + TIFFSetField(t->tiff_file, TIFFTAG_MAKE, t->vendor); + if (t->model) + TIFFSetField(t->tiff_file, TIFFTAG_MODEL, t->model); + + time(&now); + tm = localtime(&now); + sprintf(buf, + "%4d/%02d/%02d %02d:%02d:%02d", + tm->tm_year + 1900, + tm->tm_mon + 1, + tm->tm_mday, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); + TIFFSetField(t->tiff_file, TIFFTAG_DATETIME, buf); + TIFFSetField(t->tiff_file, TIFFTAG_FAXRECVTIME, now - s->page_start_time); + + TIFFSetField(t->tiff_file, TIFFTAG_IMAGELENGTH, s->image_length); + /* Set the total pages to 1. For any one page document we will get this + right. For multi-page documents we will need to come back and fill in + the right answer when we know it. */ + TIFFSetField(t->tiff_file, TIFFTAG_PAGENUMBER, s->current_page++, 1); + s->tiff.pages_in_file = s->current_page; + if (t->output_compression == COMPRESSION_CCITT_T4) + { + if (s->t4_t6_rx.bad_rows) + { + TIFFSetField(t->tiff_file, TIFFTAG_BADFAXLINES, s->t4_t6_rx.bad_rows); + TIFFSetField(t->tiff_file, TIFFTAG_CLEANFAXDATA, CLEANFAXDATA_REGENERATED); + TIFFSetField(t->tiff_file, TIFFTAG_CONSECUTIVEBADFAXLINES, s->t4_t6_rx.longest_bad_row_run); + } + else + { + TIFFSetField(t->tiff_file, TIFFTAG_CLEANFAXDATA, CLEANFAXDATA_CLEAN); + } + } + TIFFSetField(t->tiff_file, TIFFTAG_IMAGEWIDTH, s->image_width); + return 0; +} +/*- End of function --------------------------------------------------------*/ + +static int open_tiff_output_file(t4_state_t *s, const char *file) +{ + if ((s->tiff.tiff_file = TIFFOpen(file, "w")) == NULL) + return -1; + return 0; +} +/*- End of function --------------------------------------------------------*/ + +static void write_tiff_image(t4_state_t *s) +{ + /* Set up the TIFF directory info... */ + set_tiff_directory_info(s); + /* ..and then write the image... */ + if (TIFFWriteEncodedStrip(s->tiff.tiff_file, 0, s->image_buffer, s->image_length*s->bytes_per_row) < 0) + span_log(&s->logging, SPAN_LOG_WARNING, "%s: Error writing TIFF strip.\n", s->tiff.file); + /* ...then the directory entry, and libtiff is happy. */ + TIFFWriteDirectory(s->tiff.tiff_file); +} +/*- End of function --------------------------------------------------------*/ + +static int close_tiff_output_file(t4_state_t *s) +{ + int i; + t4_tiff_state_t *t; + + t = &s->tiff; + /* Perform any operations needed to tidy up a written TIFF file before + closure. */ + if (s->current_page > 1) + { + /* We need to edit the TIFF directories. Until now we did not know + the total page count, so the TIFF file currently says one. Now we + need to set the correct total page count associated with each page. */ + for (i = 0; i < s->current_page; i++) + { + TIFFSetDirectory(t->tiff_file, (tdir_t) i); + TIFFSetField(t->tiff_file, TIFFTAG_PAGENUMBER, i, s->current_page); + TIFFWriteDirectory(t->tiff_file); + } + } + TIFFClose(t->tiff_file); + t->tiff_file = NULL; + if (t->file) + { + /* Try not to leave a file behind, if we didn't receive any pages to + put in it. */ + if (s->current_page == 0) + remove(t->file); + free((char *) t->file); + t->file = NULL; + } + return 0; +} +/*- End of function --------------------------------------------------------*/ + +#else + +static int set_tiff_directory_info(t4_state_t *s) +{ + return 0; +} +/*- End of function --------------------------------------------------------*/ + +static int get_tiff_directory_info(t4_state_t *s) +{ + return 0; +} +/*- End of function --------------------------------------------------------*/ + +static int test_tiff_directory_info(t4_state_t *s) +{ + return 0; +} +/*- End of function --------------------------------------------------------*/ + +static int open_tiff_input_file(t4_state_t *s, const char *file) +{ + return 0; +} +/*- End of function --------------------------------------------------------*/ + +static int read_tiff_image(t4_state_t *s) +{ + return 0; +} +/*- End of function --------------------------------------------------------*/ + +static int close_tiff_input_file(t4_state_t *s) +{ + return 0; +} +/*- End of function --------------------------------------------------------*/ + +static int open_tiff_output_file(t4_state_t *s, const char *file) +{ + return 0; +} +/*- End of function --------------------------------------------------------*/ + +static void write_tiff_image(t4_state_t *s) +{ + return 0; +} +/*- End of function --------------------------------------------------------*/ + +static int close_tiff_output_file(t4_state_t *s) +{ + return 0; +} +/*- End of function --------------------------------------------------------*/ +#endif + +static void update_row_bit_info(t4_state_t *s) +{ + if (s->row_bits > s->max_row_bits) + s->max_row_bits = s->row_bits; + if (s->row_bits < s->min_row_bits) + s->min_row_bits = s->row_bits; + s->row_bits = 0; +} +/*- End of function --------------------------------------------------------*/ + +#if defined(__i386__) || defined(__x86_64__) || defined(__ppc__) || defined(__powerpc__) +static __inline__ int run_length(unsigned int bits) +{ + return 7 - top_bit(bits); +} +/*- End of function --------------------------------------------------------*/ +#else +static __inline__ int run_length(unsigned int bits) +{ + static const uint8_t run_len[256] = + { + 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, /* 0x00 - 0x0F */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x10 - 0x1F */ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x20 - 0x2F */ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x30 - 0x3F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 - 0x4F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x50 - 0x5F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60 - 0x6F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x70 - 0x7F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xA0 - 0xAF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xB0 - 0xBF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xC0 - 0xCF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xD0 - 0xDF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xE0 - 0xEF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xF0 - 0xFF */ + }; + + return run_len[bits]; +} +/*- End of function --------------------------------------------------------*/ +#endif + +static int free_buffers(t4_state_t *s) +{ + if (s->image_buffer) + { + free(s->image_buffer); + s->image_buffer = NULL; + s->image_buffer_size = 0; + } + if (s->cur_runs) + { + free(s->cur_runs); + s->cur_runs = NULL; + } + if (s->ref_runs) + { + free(s->ref_runs); + s->ref_runs = NULL; + } + if (s->row_buf) + { + free(s->row_buf); + s->row_buf = NULL; + } + return 0; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void add_run_to_row(t4_state_t *s) +{ + if (s->t4_t6_rx.run_length >= 0) + { + s->row_len += s->t4_t6_rx.run_length; + /* Don't allow rows to grow too long, and overflow the buffers */ + if (s->row_len <= s->image_width) + s->cur_runs[s->t4_t6_rx.a_cursor++] = s->t4_t6_rx.run_length; + } + s->t4_t6_rx.run_length = 0; +} +/*- End of function --------------------------------------------------------*/ + +static int put_decoded_row(t4_state_t *s) +{ + static const int msbmask[9] = + { + 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff + }; + uint8_t *t; + uint32_t i; + uint32_t *p; + int fudge; + int row_starts_at; + int x; + int j; + + if (s->t4_t6_rx.run_length) + add_run_to_row(s); +#if defined(T4_STATE_DEBUGGING) + /* Dump the runs of black and white for analysis */ + { + int total; + + total = 0; + for (x = 0; x < s->t4_t6_rx.b_cursor; x++) + total += s->ref_runs[x]; + printf("Ref (%d)", total); + for (x = 0; x < s->t4_t6_rx.b_cursor; x++) + printf(" %" PRIu32, s->ref_runs[x]); + printf("\n"); + total = 0; + for (x = 0; x < s->t4_t6_rx.a_cursor; x++) + total += s->cur_runs[x]; + printf("Cur (%d)", total); + for (x = 0; x < s->t4_t6_rx.a_cursor; x++) + printf(" %" PRIu32, s->cur_runs[x]); + printf("\n"); + } +#endif + row_starts_at = s->image_size; + /* Make sure there is enough room for another row */ + if (s->image_size + s->bytes_per_row >= s->image_buffer_size) + { + if ((t = realloc(s->image_buffer, s->image_buffer_size + 100*s->bytes_per_row)) == NULL) + return -1; + s->image_buffer_size += 100*s->bytes_per_row; + s->image_buffer = t; + } + if (s->row_len == s->image_width) + { + STATE_TRACE("%d Good row - %d %s\n", s->image_length, s->row_len, (s->row_is_2d) ? "2D" : "1D"); + if (s->t4_t6_rx.curr_bad_row_run) + { + if (s->t4_t6_rx.curr_bad_row_run > s->t4_t6_rx.longest_bad_row_run) + s->t4_t6_rx.longest_bad_row_run = s->t4_t6_rx.curr_bad_row_run; + s->t4_t6_rx.curr_bad_row_run = 0; + } + /* Convert the runs to a bit image of the row */ + /* White/black/white... runs, always starting with white. That means the first run could be + zero length. */ + for (x = 0, fudge = 0; x < s->t4_t6_rx.a_cursor; x++, fudge ^= 0xFF) + { + i = s->cur_runs[x]; + if ((int) i >= s->tx_bits) + { + s->tx_bitstream = (s->tx_bitstream << s->tx_bits) | (msbmask[s->tx_bits] & fudge); + for (i += (8 - s->tx_bits); i >= 8; i -= 8) + { + s->tx_bits = 8; + s->image_buffer[s->image_size++] = (uint8_t) s->tx_bitstream; + s->tx_bitstream = fudge; + } + } + s->tx_bitstream = (s->tx_bitstream << i) | (msbmask[i] & fudge); + s->tx_bits -= i; + } + s->image_length++; + } + else + { + STATE_TRACE("%d Bad row - %d %s\n", s->image_length, s->row_len, (s->row_is_2d) ? "2D" : "1D"); + /* Try to clean up the bad runs, and produce something reasonable as the reference + row for the next row. Use a copy of the previous good row as the actual current + row. If the row only fell apart near the end, reusing it might be the best + solution. */ + for (j = 0, fudge = 0; j < s->t4_t6_rx.a_cursor && fudge < s->image_width; j++) + fudge += s->cur_runs[j]; + if (fudge < s->image_width) + { + /* Try to pad with white, and avoid black, to minimise mess on the image. */ + if ((s->t4_t6_rx.a_cursor & 1)) + { + /* We currently finish in white. We could extend that, but it is probably of + the right length. Changing it would only further mess up what happens in the + next row. It seems better to add a black spot, and an extra white run. */ + s->cur_runs[s->t4_t6_rx.a_cursor++] = 1; + fudge++; + if (fudge < s->image_width) + s->cur_runs[s->t4_t6_rx.a_cursor++] = s->image_width - fudge; + } + else + { + /* We currently finish on black, so we add an extra white run to fill out the line. */ + s->cur_runs[s->t4_t6_rx.a_cursor++] = s->image_width - fudge; + } + } + else + { + /* Trim the last element to align with the proper image width */ + s->cur_runs[s->t4_t6_rx.a_cursor] += (s->image_width - fudge); + } + /* Ensure there is a previous line to copy from. */ + if (s->image_size != s->t4_t6_rx.last_row_starts_at) + { + /* Copy the previous row over this one */ + memcpy(s->image_buffer + s->image_size, s->image_buffer + s->t4_t6_rx.last_row_starts_at, s->bytes_per_row); + s->image_size += s->bytes_per_row; + s->image_length++; + } + s->t4_t6_rx.bad_rows++; + s->t4_t6_rx.curr_bad_row_run++; + } + + /* Pad the row as it becomes the reference row, so there are no odd runs to pick up if we + step off the end of the list. */ + s->cur_runs[s->t4_t6_rx.a_cursor] = 0; + s->cur_runs[s->t4_t6_rx.a_cursor + 1] = 0; + + /* Prepare the buffers for the next row. */ + s->t4_t6_rx.last_row_starts_at = row_starts_at; + /* Swap the buffers */ + p = s->cur_runs; + s->cur_runs = s->ref_runs; + s->ref_runs = p; + + s->t4_t6_rx.b_cursor = 1; + s->t4_t6_rx.a_cursor = 0; + s->t4_t6_rx.b1 = s->ref_runs[0]; + s->t4_t6_rx.a0 = 0; + + s->t4_t6_rx.run_length = 0; + + return 0; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) t4_rx_end_page(t4_state_t *s) +{ + int row; + int i; + + if (s->line_encoding == T4_COMPRESSION_ITU_T6) + { + /* Push enough zeros through the decoder to flush out any remaining codes */ + for (i = 0; i < 13; i++) + t4_rx_put_bit(s, 0); + } + if (s->t4_t6_rx.curr_bad_row_run) + { + if (s->t4_t6_rx.curr_bad_row_run > s->t4_t6_rx.longest_bad_row_run) + s->t4_t6_rx.longest_bad_row_run = s->t4_t6_rx.curr_bad_row_run; + s->t4_t6_rx.curr_bad_row_run = 0; + } + + if (s->image_size == 0) + return -1; + + if (s->t4_t6_rx.row_write_handler) + { + for (row = 0; row < s->image_length; row++) + { + if (s->t4_t6_rx.row_write_handler(s->t4_t6_rx.row_write_user_data, s->image_buffer + row*s->bytes_per_row, s->bytes_per_row) < 0) + { + span_log(&s->logging, SPAN_LOG_WARNING, "Write error at row %d.\n", row); + break; + } + } + /* Write a blank row to indicate the end of the image. */ + if (s->t4_t6_rx.row_write_handler(s->t4_t6_rx.row_write_user_data, NULL, 0) < 0) + span_log(&s->logging, SPAN_LOG_WARNING, "Write error at row %d.\n", row); + } + else + { + write_tiff_image(s); + } + s->t4_t6_rx.rx_bits = 0; + s->t4_t6_rx.rx_skip_bits = 0; + s->t4_t6_rx.rx_bitstream = 0; + s->t4_t6_rx.consecutive_eols = EOLS_TO_END_ANY_RX_PAGE; + + s->image_size = 0; + return 0; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void drop_rx_bits(t4_state_t *s, int bits) +{ + /* Only remove one bit right now. The rest need to be removed step by step, + checking for a misaligned EOL along the way. This is time consuming, but + if we don't do it a single bit error can severely damage an image. */ + s->row_bits += bits; + s->t4_t6_rx.rx_skip_bits += (bits - 1); + s->t4_t6_rx.rx_bits--; + s->t4_t6_rx.rx_bitstream >>= 1; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void force_drop_rx_bits(t4_state_t *s, int bits) +{ + /* This should only be called to drop the bits of an EOL, as that is the + only place where it is safe to drop them all at once. */ + s->row_bits += bits; + s->t4_t6_rx.rx_skip_bits = 0; + s->t4_t6_rx.rx_bits -= bits; + s->t4_t6_rx.rx_bitstream >>= bits; +} +/*- End of function --------------------------------------------------------*/ + +static int rx_put_bits(t4_state_t *s, uint32_t bit_string, int quantity) +{ + int bits; + + /* We decompress bit by bit, as the data stream is received. We need to + scan continuously for EOLs, so we might as well work this way. */ + s->line_image_size += quantity; + s->t4_t6_rx.rx_bitstream |= (bit_string << s->t4_t6_rx.rx_bits); + /* The longest item we need to scan for is 13 bits long (a 2D EOL), so we + need a minimum of 13 bits in the buffer to proceed with any bit stream + analysis. */ + if ((s->t4_t6_rx.rx_bits += quantity) < 13) + return FALSE; + if (s->t4_t6_rx.consecutive_eols) + { + /* Check if the image has already terminated. */ + if (s->t4_t6_rx.consecutive_eols >= EOLS_TO_END_ANY_RX_PAGE) + return TRUE; + /* Check if the image hasn't even started. */ + if (s->t4_t6_rx.consecutive_eols < 0) + { + /* We are waiting for the very first EOL (1D or 2D only). */ + /* We need to take this bit by bit, as the EOL could be anywhere, + and any junk could preceed it. */ + while ((s->t4_t6_rx.rx_bitstream & 0xFFF) != 0x800) + { + s->t4_t6_rx.rx_bitstream >>= 1; + if (--s->t4_t6_rx.rx_bits < 13) + return FALSE; + } + /* We have an EOL, so now the page begins and we can proceed to + process the bit stream as image data. */ + s->t4_t6_rx.consecutive_eols = 0; + if (s->line_encoding == T4_COMPRESSION_ITU_T4_1D) + { + s->row_is_2d = FALSE; + force_drop_rx_bits(s, 12); + } + else + { + s->row_is_2d = !(s->t4_t6_rx.rx_bitstream & 0x1000); + force_drop_rx_bits(s, 13); + } + } + } + + while (s->t4_t6_rx.rx_bits >= 13) + { + /* We need to check for EOLs bit by bit through the whole stream. If + we just try looking between code words, we will miss an EOL when a bit + error has throw the code words completely out of step. The can mean + recovery takes many lines, and the image gets really messed up. */ + /* Although EOLs are not inserted at the end of each row of a T.6 image, + they are still perfectly valid, and can terminate an image. */ + if ((s->t4_t6_rx.rx_bitstream & 0x0FFF) == 0x0800) + { + STATE_TRACE("EOL\n"); + if (s->row_len == 0) + { + /* A zero length row - i.e. 2 consecutive EOLs - is distinctly + the end of page condition. That's all we actually get on a + T.6 page. However, there are a minimum of 6 EOLs at the end of + any T.4 page. We can look for more than 2 EOLs in case bit + errors simulate the end of page condition at the wrong point. + Such robust checking is irrelevant for a T.6 page, as it should + be error free. */ + /* Note that for a T.6 page we should get here on the very first + EOL, as the row length should be zero at that point. Therefore + we should count up both EOLs, unless there is some bogus partial + row ahead of them. */ + s->t4_t6_rx.consecutive_eols++; + if (s->line_encoding == T4_COMPRESSION_ITU_T6) + { + if (s->t4_t6_rx.consecutive_eols >= EOLS_TO_END_T6_RX_PAGE) + { + s->t4_t6_rx.consecutive_eols = EOLS_TO_END_ANY_RX_PAGE; + return TRUE; + } + } + else + { + if (s->t4_t6_rx.consecutive_eols >= EOLS_TO_END_T4_RX_PAGE) + { + s->t4_t6_rx.consecutive_eols = EOLS_TO_END_ANY_RX_PAGE; + return TRUE; + } + } + } + else + { + /* The EOLs are not back-to-back, so they are not part of the + end of page condition. */ + if (s->t4_t6_rx.run_length > 0) + add_run_to_row(s); + s->t4_t6_rx.consecutive_eols = 0; + if (put_decoded_row(s)) + return TRUE; + update_row_bit_info(s); + } + if (s->line_encoding == T4_COMPRESSION_ITU_T4_2D) + { + s->row_is_2d = !(s->t4_t6_rx.rx_bitstream & 0x1000); + force_drop_rx_bits(s, 13); + } + else + { + force_drop_rx_bits(s, 12); + } + s->t4_t6_rx.its_black = FALSE; + s->t4_t6_rx.black_white = 0; + s->t4_t6_rx.run_length = 0; + s->row_len = 0; + continue; + } + if (s->t4_t6_rx.rx_skip_bits) + { + /* We are clearing out the remaining bits of the last code word we + absorbed. */ + s->t4_t6_rx.rx_skip_bits--; + s->t4_t6_rx.rx_bits--; + s->t4_t6_rx.rx_bitstream >>= 1; + continue; + } + if (s->row_is_2d && s->t4_t6_rx.black_white == 0) + { + bits = s->t4_t6_rx.rx_bitstream & 0x7F; + STATE_TRACE("State %d, %d - ", + t4_2d_table[bits].state, + t4_2d_table[bits].width); + if (s->row_len >= s->image_width) + { + drop_rx_bits(s, t4_2d_table[bits].width); + continue; + } + if (s->t4_t6_rx.a_cursor) + { + /* Move past a0, always staying on the current colour */ + for ( ; s->t4_t6_rx.b1 <= s->t4_t6_rx.a0; s->t4_t6_rx.b_cursor += 2) + s->t4_t6_rx.b1 += (s->ref_runs[s->t4_t6_rx.b_cursor] + s->ref_runs[s->t4_t6_rx.b_cursor + 1]); + } + switch (t4_2d_table[bits].state) + { + case S_Horiz: + STATE_TRACE("Horiz %d %d %d\n", + s->image_width, + s->t4_t6_rx.a0, + s->t4_t6_rx.a_cursor); + /* We now need to extract a white/black or black/white pair of runs, using the 1D + method. If the first of the pair takes us exactly to the end of the row, there + should still be a zero length element for the second of the pair. */ + s->t4_t6_rx.its_black = s->t4_t6_rx.a_cursor & 1; + s->t4_t6_rx.black_white = 2; + break; + case S_Vert: + STATE_TRACE("Vert[%d] %d %d %d %d\n", + t4_2d_table[bits].param, + s->image_width, + s->t4_t6_rx.a0, + s->t4_t6_rx.b1, + s->t4_t6_rx.run_length); + s->t4_t6_rx.run_length += (s->t4_t6_rx.b1 - s->t4_t6_rx.a0 + t4_2d_table[bits].param); + s->t4_t6_rx.a0 = s->t4_t6_rx.b1 + t4_2d_table[bits].param; + add_run_to_row(s); + /* We need to move one step in one direction or the other, to change to the + opposite colour */ + if (t4_2d_table[bits].param >= 0) + { + s->t4_t6_rx.b1 += s->ref_runs[s->t4_t6_rx.b_cursor++]; + } + else + { + if (s->t4_t6_rx.b_cursor) + s->t4_t6_rx.b1 -= s->ref_runs[--s->t4_t6_rx.b_cursor]; + } + break; + case S_Pass: + STATE_TRACE("Pass %d %d %d %d %d\n", + s->image_width, + s->t4_t6_rx.a0, + s->t4_t6_rx.b1, + s->ref_runs[s->t4_t6_rx.b_cursor], + s->ref_runs[s->t4_t6_rx.b_cursor + 1]); + s->t4_t6_rx.b1 += s->ref_runs[s->t4_t6_rx.b_cursor++]; + s->t4_t6_rx.run_length += (s->t4_t6_rx.b1 - s->t4_t6_rx.a0); + s->t4_t6_rx.a0 = s->t4_t6_rx.b1; + s->t4_t6_rx.b1 += s->ref_runs[s->t4_t6_rx.b_cursor++]; + break; + case S_Ext: + /* We do not currently handle any kind of extension */ + STATE_TRACE("Ext %d %d %d 0x%x\n", + s->image_width, + s->t4_t6_rx.a0, + ((s->t4_t6_rx.rx_bitstream >> t4_2d_table[bits].width) & 0x7), + s->t4_t6_rx.rx_bitstream); + /* TODO: The uncompressed option should be implemented. */ + break; + case S_Null: + STATE_TRACE("Null\n"); + break; + default: + STATE_TRACE("Unexpected T.4 state\n"); + span_log(&s->logging, SPAN_LOG_WARNING, "Unexpected T.4 state %d\n", t4_2d_table[bits].state); + break; + } + drop_rx_bits(s, t4_2d_table[bits].width); + } + else + { + if (s->t4_t6_rx.its_black) + { + bits = s->t4_t6_rx.rx_bitstream & 0x1FFF; + STATE_TRACE("State %d, %d - Black %d %d %d\n", + t4_1d_black_table[bits].state, + t4_1d_black_table[bits].width, + s->image_width, + s->t4_t6_rx.a0, + t4_1d_black_table[bits].param); + switch (t4_1d_black_table[bits].state) + { + case S_MakeUpB: + case S_MakeUp: + s->t4_t6_rx.run_length += t4_1d_black_table[bits].param; + s->t4_t6_rx.a0 += t4_1d_black_table[bits].param; + break; + case S_TermB: + s->t4_t6_rx.its_black = FALSE; + if (s->row_len < s->image_width) + { + s->t4_t6_rx.run_length += t4_1d_black_table[bits].param; + s->t4_t6_rx.a0 += t4_1d_black_table[bits].param; + add_run_to_row(s); + } + if (s->t4_t6_rx.black_white) + s->t4_t6_rx.black_white--; + break; + default: + /* Bad black */ + s->t4_t6_rx.black_white = 0; + break; + } + drop_rx_bits(s, t4_1d_black_table[bits].width); + } + else + { + bits = s->t4_t6_rx.rx_bitstream & 0xFFF; + STATE_TRACE("State %d, %d - White %d %d %d\n", + t4_1d_white_table[bits].state, + t4_1d_white_table[bits].width, + s->image_width, + s->t4_t6_rx.a0, + t4_1d_white_table[bits].param); + switch (t4_1d_white_table[bits].state) + { + case S_MakeUpW: + case S_MakeUp: + s->t4_t6_rx.run_length += t4_1d_white_table[bits].param; + s->t4_t6_rx.a0 += t4_1d_white_table[bits].param; + break; + case S_TermW: + s->t4_t6_rx.its_black = TRUE; + if (s->row_len < s->image_width) + { + s->t4_t6_rx.run_length += t4_1d_white_table[bits].param; + s->t4_t6_rx.a0 += t4_1d_white_table[bits].param; + add_run_to_row(s); + } + if (s->t4_t6_rx.black_white) + s->t4_t6_rx.black_white--; + break; + default: + /* Bad white */ + s->t4_t6_rx.black_white = 0; + break; + } + drop_rx_bits(s, t4_1d_white_table[bits].width); + } + } + if (s->t4_t6_rx.a0 >= s->image_width) + s->t4_t6_rx.a0 = s->image_width - 1; + + if (s->line_encoding == T4_COMPRESSION_ITU_T6) + { + /* T.6 has no EOL markers. We sense the end of a line by its length alone. */ + /* The last test here is a backstop protection, so a corrupt image cannot + cause us to do bad things. Bad encoders have actually been seen, which + demand such protection. */ + if (s->t4_t6_rx.black_white == 0 && s->row_len >= s->image_width) + { + STATE_TRACE("EOL T.6\n"); + if (s->t4_t6_rx.run_length > 0) + add_run_to_row(s); + update_row_bit_info(s); + if (put_decoded_row(s)) + return TRUE; + s->t4_t6_rx.its_black = FALSE; + s->t4_t6_rx.black_white = 0; + s->t4_t6_rx.run_length = 0; + s->row_len = 0; + } + } + } + return FALSE; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) t4_rx_put_bit(t4_state_t *s, int bit) +{ + return rx_put_bits(s, bit & 1, 1); +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) t4_rx_put_byte(t4_state_t *s, uint8_t byte) +{ + return rx_put_bits(s, byte & 0xFF, 8); +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) t4_rx_put_chunk(t4_state_t *s, const uint8_t buf[], int len) +{ + int i; + uint8_t byte; + + for (i = 0; i < len; i++) + { + byte = buf[i]; + if (rx_put_bits(s, byte & 0xFF, 8)) + return TRUE; + } + return FALSE; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) t4_rx_set_row_write_handler(t4_state_t *s, t4_row_write_handler_t handler, void *user_data) +{ + s->t4_t6_rx.row_write_handler = handler; + s->t4_t6_rx.row_write_user_data = user_data; + return 0; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(t4_state_t *) t4_rx_init(t4_state_t *s, const char *file, int output_encoding) +{ + if (s == NULL) + { + if ((s = (t4_state_t *) malloc(sizeof(*s))) == NULL) + return NULL; + } + memset(s, 0, sizeof(*s)); + span_log_init(&s->logging, SPAN_LOG_NONE, NULL); + span_log_set_protocol(&s->logging, "T.4"); + s->rx = TRUE; + + span_log(&s->logging, SPAN_LOG_FLOW, "Start rx document\n"); + + if (open_tiff_output_file(s, file) < 0) + return NULL; + + /* Save the file name for logging reports. */ + s->tiff.file = strdup(file); + /* Only provide for one form of coding throughout the file, even though the + coding on the wire could change between pages. */ + switch (output_encoding) + { + case T4_COMPRESSION_ITU_T4_1D: + s->tiff.output_compression = COMPRESSION_CCITT_T4; + s->tiff.output_t4_options = GROUP3OPT_FILLBITS; + break; + case T4_COMPRESSION_ITU_T4_2D: + s->tiff.output_compression = COMPRESSION_CCITT_T4; + s->tiff.output_t4_options = GROUP3OPT_FILLBITS | GROUP3OPT_2DENCODING; + break; + case T4_COMPRESSION_ITU_T6: + s->tiff.output_compression = COMPRESSION_CCITT_T6; + s->tiff.output_t4_options = 0; + break; + } + + /* Until we have a valid figure for the bytes per row, we need it to be set to a suitable + value to ensure it will be seen as changing when the real value is used. */ + s->bytes_per_row = 0; + + s->current_page = 0; + s->tiff.pages_in_file = 0; + s->tiff.start_page = 0; + s->tiff.stop_page = INT_MAX; + + s->image_buffer = NULL; + s->image_buffer_size = 0; + + /* Set some default values */ + s->x_resolution = T4_X_RESOLUTION_R8; + s->y_resolution = T4_Y_RESOLUTION_FINE; + s->image_width = T4_WIDTH_R8_A4; + + return s; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) t4_rx_start_page(t4_state_t *s) +{ + int bytes_per_row; + int run_space; + uint32_t *bufptr; + + span_log(&s->logging, SPAN_LOG_FLOW, "Start rx page - compression %d\n", s->line_encoding); + if (s->tiff.tiff_file == NULL) + return -1; + + /* Calculate the scanline/tile width. */ + bytes_per_row = (s->image_width + 7)/8; + run_space = (s->image_width + 4)*sizeof(uint32_t); + if (bytes_per_row != s->bytes_per_row) + { + /* Allocate the space required for decoding the new row length. */ + s->bytes_per_row = bytes_per_row; + if ((bufptr = (uint32_t *) realloc(s->cur_runs, run_space)) == NULL) + return -1; + s->cur_runs = bufptr; + if ((bufptr = (uint32_t *) realloc(s->ref_runs, run_space)) == NULL) + return -1; + s->ref_runs = bufptr; + } + memset(s->cur_runs, 0, run_space); + memset(s->ref_runs, 0, run_space); + + s->t4_t6_rx.rx_bits = 0; + s->t4_t6_rx.rx_skip_bits = 0; + s->t4_t6_rx.rx_bitstream = 0; + s->row_bits = 0; + s->min_row_bits = INT_MAX; + s->max_row_bits = 0; + + s->row_is_2d = (s->line_encoding == T4_COMPRESSION_ITU_T6); + /* We start at -1 EOLs for 1D and 2D decoding, as an indication we are waiting for the + first EOL. T.6 coding starts without any preamble. */ + s->t4_t6_rx.consecutive_eols = (s->line_encoding == T4_COMPRESSION_ITU_T6) ? 0 : -1; + + s->t4_t6_rx.bad_rows = 0; + s->t4_t6_rx.longest_bad_row_run = 0; + s->t4_t6_rx.curr_bad_row_run = 0; + s->image_length = 0; + s->tx_bitstream = 0; + s->tx_bits = 8; + s->image_size = 0; + s->line_image_size = 0; + s->t4_t6_rx.last_row_starts_at = 0; + + s->row_len = 0; + s->t4_t6_rx.its_black = FALSE; + s->t4_t6_rx.black_white = 0; + + /* Initialise the reference line to all white */ + s->ref_runs[0] = + s->ref_runs[1] = + s->ref_runs[2] = + s->ref_runs[3] = s->image_width; + + s->t4_t6_rx.b_cursor = 1; + s->t4_t6_rx.a_cursor = 0; + s->t4_t6_rx.b1 = s->ref_runs[0]; + s->t4_t6_rx.a0 = 0; + + s->t4_t6_rx.run_length = 0; + + time (&s->page_start_time); + + return 0; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) t4_rx_release(t4_state_t *s) +{ + if (!s->rx) + return -1; + if (s->tiff.tiff_file) + close_tiff_output_file(s); + free_buffers(s); + return 0; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) t4_rx_free(t4_state_t *s) +{ + int ret; + + ret = t4_rx_release(s); + free(s); + return ret; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(void) t4_rx_set_rx_encoding(t4_state_t *s, int encoding) +{ + s->line_encoding = encoding; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(void) t4_rx_set_image_width(t4_state_t *s, int width) +{ + s->image_width = width; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(void) t4_rx_set_y_resolution(t4_state_t *s, int resolution) +{ + s->y_resolution = resolution; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(void) t4_rx_set_x_resolution(t4_state_t *s, int resolution) +{ + s->x_resolution = resolution; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(void) t4_rx_set_dcs(t4_state_t *s, const char *dcs) +{ + s->tiff.dcs = (dcs && dcs[0]) ? dcs : NULL; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(void) t4_rx_set_sub_address(t4_state_t *s, const char *sub_address) +{ + s->tiff.sub_address = (sub_address && sub_address[0]) ? sub_address : NULL; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(void) t4_rx_set_far_ident(t4_state_t *s, const char *ident) +{ + s->tiff.far_ident = (ident && ident[0]) ? ident : NULL; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(void) t4_rx_set_vendor(t4_state_t *s, const char *vendor) +{ + s->tiff.vendor = vendor; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(void) t4_rx_set_model(t4_state_t *s, const char *model) +{ + s->tiff.model = model; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(void) t4_get_transfer_statistics(t4_state_t *s, t4_stats_t *t) +{ + t->pages_transferred = s->current_page - s->tiff.start_page; + t->pages_in_file = s->tiff.pages_in_file; + t->width = s->image_width; + t->length = s->image_length; + t->bad_rows = s->t4_t6_rx.bad_rows; + t->longest_bad_row_run = s->t4_t6_rx.longest_bad_row_run; + t->x_resolution = s->x_resolution; + t->y_resolution = s->y_resolution; + t->encoding = s->line_encoding; + t->line_image_size = s->line_image_size/8; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(const char *) t4_encoding_to_str(int encoding) +{ + switch (encoding) + { + case T4_COMPRESSION_ITU_T4_1D: + return "T.4 1-D"; + case T4_COMPRESSION_ITU_T4_2D: + return "T.4 2-D"; + case T4_COMPRESSION_ITU_T6: + return "T.6"; + } + return "???"; +} +/*- End of function --------------------------------------------------------*/ +/*- End of file ------------------------------------------------------------*/