kopia lustrzana https://gitlab.com/sane-project/backends
				
				
				
			
		
			
				
	
	
		
			1941 wiersze
		
	
	
		
			47 KiB
		
	
	
	
		
			C
		
	
	
			
		
		
	
	
			1941 wiersze
		
	
	
		
			47 KiB
		
	
	
	
		
			C
		
	
	
/*
 | 
						|
 * sanei_magic - Image processing functions for despeckle, deskew, and autocrop
 | 
						|
 | 
						|
   Copyright (C) 2009 m. allan noah
 | 
						|
 | 
						|
   This file is part of the SANE package.
 | 
						|
 | 
						|
   This program 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 of the
 | 
						|
   License, or (at your option) any later version.
 | 
						|
 | 
						|
   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
 | 
						|
   General Public License for more details.
 | 
						|
 | 
						|
   You should have received a copy of the GNU General Public License
 | 
						|
   along with this program; if not, write to the Free Software
 | 
						|
   Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 | 
						|
   MA 02111-1307, USA.
 | 
						|
 | 
						|
   As a special exception, the authors of SANE give permission for
 | 
						|
   additional uses of the libraries contained in this release of SANE.
 | 
						|
 | 
						|
   The exception is that, if you link a SANE library with other files
 | 
						|
   to produce an executable, this does not by itself cause the
 | 
						|
   resulting executable to be covered by the GNU General Public
 | 
						|
   License.  Your use of that executable is in no way restricted on
 | 
						|
   account of linking the SANE library code into it.
 | 
						|
 | 
						|
   This exception does not, however, invalidate any other reasons why
 | 
						|
   the executable file might be covered by the GNU General Public
 | 
						|
   License.
 | 
						|
 | 
						|
   If you submit changes to SANE to the maintainers to be included in
 | 
						|
   a subsequent release, you agree by submitting the changes that
 | 
						|
   those changes may be distributed with this exception intact.
 | 
						|
 | 
						|
   If you write modifications of your own for SANE, it is your choice
 | 
						|
   whether to permit this exception to apply to your modifications.
 | 
						|
   If you do not wish that, delete this exception notice.
 | 
						|
 */
 | 
						|
 | 
						|
#include "../include/sane/config.h"
 | 
						|
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <errno.h>
 | 
						|
#include <math.h>
 | 
						|
 | 
						|
#define BACKEND_NAME sanei_magic      /* name of this module for debugging */
 | 
						|
 | 
						|
#include "../include/sane/sane.h"
 | 
						|
#include "../include/sane/sanei_debug.h"
 | 
						|
#include "../include/sane/sanei_magic.h"
 | 
						|
 | 
						|
/* prototypes for utility functions defined at bottom of file */
 | 
						|
int * sanei_magic_getTransY (
 | 
						|
  SANE_Parameters * params, int dpi, SANE_Byte * buffer, int top);
 | 
						|
 | 
						|
int * sanei_magic_getTransX (
 | 
						|
  SANE_Parameters * params, int dpi, SANE_Byte * buffer, int left);
 | 
						|
 | 
						|
static SANE_Status getTopEdge (int width, int height, int resolution,
 | 
						|
  int * buff, double * finSlope, int * finXInter, int * finYInter);
 | 
						|
 | 
						|
static SANE_Status getLeftEdge (int width, int height, int * top, int * bot,
 | 
						|
 double slope, int * finXInter, int * finYInter);
 | 
						|
 | 
						|
static SANE_Status getLine (int height, int width, int * buff,
 | 
						|
  int slopes, double minSlope, double maxSlope,
 | 
						|
  int offsets, int minOffset, int maxOffset,
 | 
						|
  double * finSlope, int * finOffset, int * finDensity);
 | 
						|
 | 
						|
void
 | 
						|
sanei_magic_init( void )
 | 
						|
{
 | 
						|
  DBG_INIT();
 | 
						|
}
 | 
						|
 | 
						|
/* find small spots and replace them with image background color */
 | 
						|
SANE_Status
 | 
						|
sanei_magic_despeck (SANE_Parameters * params, SANE_Byte * buffer,
 | 
						|
  SANE_Int diam)
 | 
						|
{
 | 
						|
 | 
						|
  SANE_Status ret = SANE_STATUS_GOOD;
 | 
						|
 | 
						|
  int pw = params->pixels_per_line;
 | 
						|
  int bw = params->bytes_per_line;
 | 
						|
  int h  = params->lines;
 | 
						|
  int bt = bw*h;
 | 
						|
 | 
						|
  int i,j,k,l,n;
 | 
						|
 | 
						|
  DBG (10, "sanei_magic_despeck: start\n");
 | 
						|
 | 
						|
  if(params->format == SANE_FRAME_RGB){
 | 
						|
 | 
						|
    for(i=bw; i<bt-bw-(bw*diam); i+=bw){
 | 
						|
      for(j=1; j<pw-1-diam; j++){
 | 
						|
 | 
						|
        int thresh = 255*3;
 | 
						|
        int outer[] = {0,0,0};
 | 
						|
        int hits = 0;
 | 
						|
 | 
						|
        /* loop over rows and columns in window */
 | 
						|
        /* find darkest pixel */
 | 
						|
        for(k=0; k<diam; k++){
 | 
						|
          for(l=0; l<diam; l++){
 | 
						|
            int tmp = 0;
 | 
						|
 | 
						|
            for(n=0; n<3; n++){
 | 
						|
              tmp += buffer[i + j*3 + k*bw + l*3 + n];
 | 
						|
            }
 | 
						|
 | 
						|
            if(tmp < thresh)
 | 
						|
              thresh = tmp;
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        /* convert darkest pixel into a brighter threshold */
 | 
						|
        thresh = (thresh + 255*3 + 255*3)/3;
 | 
						|
 | 
						|
        /*loop over rows and columns around window */
 | 
						|
        for(k=-1; k<diam+1; k++){
 | 
						|
          for(l=-1; l<diam+1; l++){
 | 
						|
 | 
						|
            int tmp[3];
 | 
						|
 | 
						|
            /* dont count pixels in the window */
 | 
						|
            if(k != -1 && k != diam && l != -1 && l != diam)
 | 
						|
              continue;
 | 
						|
 | 
						|
            for(n=0; n<3; n++){
 | 
						|
              tmp[n] = buffer[i + j*3 + k*bw + l*3 + n];
 | 
						|
              outer[n] += tmp[n];
 | 
						|
            }
 | 
						|
            if(tmp[0]+tmp[1]+tmp[2] < thresh){
 | 
						|
              hits++;
 | 
						|
              break;
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        /*no hits, overwrite with avg surrounding color*/
 | 
						|
        if(!hits){
 | 
						|
 | 
						|
          /* per channel replacement color */
 | 
						|
          for(n=0; n<3; n++){
 | 
						|
            outer[n] /= (4*diam + 4);
 | 
						|
          }
 | 
						|
 | 
						|
          for(k=0; k<diam; k++){
 | 
						|
            for(l=0; l<diam; l++){
 | 
						|
              for(n=0; n<3; n++){
 | 
						|
                buffer[i + j*3 + k*bw + l*3 + n] = outer[n];
 | 
						|
              }
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  else if(params->format == SANE_FRAME_GRAY && params->depth == 8){
 | 
						|
    for(i=bw; i<bt-bw-(bw*diam); i+=bw){
 | 
						|
      for(j=1; j<pw-1-diam; j++){
 | 
						|
 | 
						|
        int thresh = 255;
 | 
						|
        int outer = 0;
 | 
						|
        int hits = 0;
 | 
						|
 | 
						|
        for(k=0; k<diam; k++){
 | 
						|
          for(l=0; l<diam; l++){
 | 
						|
            if(buffer[i + j + k*bw + l] < thresh)
 | 
						|
              thresh = buffer[i + j + k*bw + l];
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        /* convert darkest pixel into a brighter threshold */
 | 
						|
        thresh = (thresh + 255 + 255)/3;
 | 
						|
 | 
						|
        /*loop over rows and columns around window */
 | 
						|
        for(k=-1; k<diam+1; k++){
 | 
						|
          for(l=-1; l<diam+1; l++){
 | 
						|
 | 
						|
            int tmp = 0;
 | 
						|
 | 
						|
            /* dont count pixels in the window */
 | 
						|
            if(k != -1 && k != diam && l != -1 && l != diam)
 | 
						|
              continue;
 | 
						|
 | 
						|
            tmp = buffer[i + j + k*bw + l];
 | 
						|
 | 
						|
            if(tmp < thresh){
 | 
						|
              hits++;
 | 
						|
              break;
 | 
						|
            }
 | 
						|
 | 
						|
            outer += tmp;
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        /*no hits, overwrite with avg surrounding color*/
 | 
						|
        if(!hits){
 | 
						|
          /* replacement color */
 | 
						|
          outer /= (4*diam + 4);
 | 
						|
 | 
						|
          for(k=0; k<diam; k++){
 | 
						|
            for(l=0; l<diam; l++){
 | 
						|
              buffer[i + j + k*bw + l] = outer;
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  else if(params->format == SANE_FRAME_GRAY && params->depth == 1){
 | 
						|
    for(i=bw; i<bt-bw-(bw*diam); i+=bw){
 | 
						|
      for(j=1; j<pw-1-diam; j++){
 | 
						|
 | 
						|
        int curr = 0;
 | 
						|
        int hits = 0;
 | 
						|
 | 
						|
        for(k=0; k<diam; k++){
 | 
						|
          for(l=0; l<diam; l++){
 | 
						|
            curr += buffer[i + k*bw + (j+l)/8] >> (7-(j+l)%8) & 1;
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        if(!curr)
 | 
						|
          continue;
 | 
						|
 | 
						|
        /*loop over rows and columns around window */
 | 
						|
        for(k=-1; k<diam+1; k++){
 | 
						|
          for(l=-1; l<diam+1; l++){
 | 
						|
 | 
						|
            /* dont count pixels in the window */
 | 
						|
            if(k != -1 && k != diam && l != -1 && l != diam)
 | 
						|
              continue;
 | 
						|
 | 
						|
            hits += buffer[i + k*bw + (j+l)/8] >> (7-(j+l)%8) & 1;
 | 
						|
 | 
						|
            if(hits)
 | 
						|
              break;
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        /*no hits, overwrite with white*/
 | 
						|
        if(!hits){
 | 
						|
          for(k=0; k<diam; k++){
 | 
						|
            for(l=0; l<diam; l++){
 | 
						|
              buffer[i + k*bw + (j+l)/8] &= ~(1 << (7-(j+l)%8));
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  else{
 | 
						|
    DBG (5, "sanei_magic_despeck: unsupported format/depth\n");
 | 
						|
    ret = SANE_STATUS_INVAL;
 | 
						|
  }
 | 
						|
 | 
						|
  DBG (10, "sanei_magic_despeck: finish\n");
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
/* find likely edges of media inside image background color */
 | 
						|
SANE_Status
 | 
						|
sanei_magic_findEdges(SANE_Parameters * params, SANE_Byte * buffer,
 | 
						|
  int dpiX, int dpiY, int * top, int * bot, int * left, int * right)
 | 
						|
{
 | 
						|
 | 
						|
  SANE_Status ret = SANE_STATUS_GOOD;
 | 
						|
 | 
						|
  int width = params->pixels_per_line;
 | 
						|
  int height = params->lines;
 | 
						|
 | 
						|
  int * topBuf = NULL, * botBuf = NULL;
 | 
						|
  int * leftBuf = NULL, * rightBuf = NULL;
 | 
						|
 | 
						|
  int topCount = 0, botCount = 0;
 | 
						|
  int leftCount = 0, rightCount = 0;
 | 
						|
 | 
						|
  int i;
 | 
						|
 | 
						|
  DBG (10, "sanei_magic_findEdges: start\n");
 | 
						|
 | 
						|
  /* get buffers to find sides and bottom */
 | 
						|
  topBuf = sanei_magic_getTransY(params,dpiY,buffer,1);
 | 
						|
  if(!topBuf){
 | 
						|
    DBG (5, "sanei_magic_findEdges: no topBuf\n");
 | 
						|
    ret = SANE_STATUS_NO_MEM;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  botBuf = sanei_magic_getTransY(params,dpiY,buffer,0);
 | 
						|
  if(!botBuf){
 | 
						|
    DBG (5, "sanei_magic_findEdges: no botBuf\n");
 | 
						|
    ret = SANE_STATUS_NO_MEM;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  leftBuf = sanei_magic_getTransX(params,dpiX,buffer,1);
 | 
						|
  if(!leftBuf){
 | 
						|
    DBG (5, "sanei_magic_findEdges: no leftBuf\n");
 | 
						|
    ret = SANE_STATUS_NO_MEM;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  rightBuf = sanei_magic_getTransX(params,dpiX,buffer,0);
 | 
						|
  if(!rightBuf){
 | 
						|
    DBG (5, "sanei_magic_findEdges: no rightBuf\n");
 | 
						|
    ret = SANE_STATUS_NO_MEM;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  /* loop thru left and right lists, look for top and bottom extremes */
 | 
						|
  *top = height;
 | 
						|
  for(i=0; i<height; i++){
 | 
						|
    if(rightBuf[i] > leftBuf[i]){
 | 
						|
      if(*top > i){
 | 
						|
        *top = i;
 | 
						|
      }
 | 
						|
 | 
						|
      topCount++;
 | 
						|
      if(topCount > 3){
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    else{
 | 
						|
      topCount = 0;
 | 
						|
      *top = height;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  *bot = -1;
 | 
						|
  for(i=height-1; i>=0; i--){
 | 
						|
    if(rightBuf[i] > leftBuf[i]){
 | 
						|
      if(*bot < i){
 | 
						|
        *bot = i;
 | 
						|
      }
 | 
						|
 | 
						|
      botCount++;
 | 
						|
      if(botCount > 3){
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    else{
 | 
						|
      botCount = 0;
 | 
						|
      *bot = -1;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /* could not find top/bot edges */
 | 
						|
  if(*top > *bot){
 | 
						|
    DBG (5, "sanei_magic_findEdges: bad t/b edges\n");
 | 
						|
    ret = SANE_STATUS_UNSUPPORTED;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  /* loop thru top and bottom lists, look for l and r extremes
 | 
						|
   * NOTE: We dont look above the top or below the bottom found previously.
 | 
						|
   * This prevents issues with adf scanners that pad the image after the
 | 
						|
   * paper runs out (usually with white) */
 | 
						|
  DBG (5, "sanei_magic_findEdges: bb0:%d tb0:%d b:%d t:%d\n",
 | 
						|
    botBuf[0], topBuf[0], *bot, *top);
 | 
						|
 | 
						|
  *left = width;
 | 
						|
  for(i=0; i<width; i++){
 | 
						|
    if(botBuf[i] > topBuf[i] && (botBuf[i]-10 < *bot || topBuf[i]+10 > *top)){
 | 
						|
      if(*left > i){
 | 
						|
        *left = i;
 | 
						|
      }
 | 
						|
 | 
						|
      leftCount++;
 | 
						|
      if(leftCount > 3){
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    else{
 | 
						|
      leftCount = 0;
 | 
						|
      *left = width;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  *right = -1;
 | 
						|
  for(i=width-1; i>=0; i--){
 | 
						|
    if(botBuf[i] > topBuf[i] && (botBuf[i]-10 < *bot || topBuf[i]+10 > *top)){
 | 
						|
      if(*right < i){
 | 
						|
        *right = i;
 | 
						|
      }
 | 
						|
 | 
						|
      rightCount++;
 | 
						|
      if(rightCount > 3){
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    else{
 | 
						|
      rightCount = 0;
 | 
						|
      *right = -1;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /* could not find left/right edges */
 | 
						|
  if(*left > *right){
 | 
						|
    DBG (5, "sanei_magic_findEdges: bad l/r edges\n");
 | 
						|
    ret = SANE_STATUS_UNSUPPORTED;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  DBG (15, "sanei_magic_findEdges: t:%d b:%d l:%d r:%d\n",
 | 
						|
    *top,*bot,*left,*right);
 | 
						|
 | 
						|
  cleanup:
 | 
						|
  if(topBuf)
 | 
						|
    free(topBuf);
 | 
						|
  if(botBuf)
 | 
						|
    free(botBuf);
 | 
						|
  if(leftBuf)
 | 
						|
    free(leftBuf);
 | 
						|
  if(rightBuf)
 | 
						|
    free(rightBuf);
 | 
						|
 | 
						|
  DBG (10, "sanei_magic_findEdges: finish\n");
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
/* crop image to given size. updates params with new dimensions */
 | 
						|
SANE_Status
 | 
						|
sanei_magic_crop(SANE_Parameters * params, SANE_Byte * buffer,
 | 
						|
  int top, int bot, int left, int right)
 | 
						|
{
 | 
						|
 | 
						|
  SANE_Status ret = SANE_STATUS_GOOD;
 | 
						|
 | 
						|
  int bwidth = params->bytes_per_line;
 | 
						|
 | 
						|
  int pixels = 0;
 | 
						|
  int bytes = 0;
 | 
						|
  unsigned char * line = NULL;
 | 
						|
  int pos = 0, i;
 | 
						|
 | 
						|
  DBG (10, "sanei_magic_crop: start\n");
 | 
						|
 | 
						|
  /*convert left and right to bytes, figure new byte and pixel width */
 | 
						|
  if(params->format == SANE_FRAME_RGB){
 | 
						|
    pixels = right-left;
 | 
						|
    bytes = pixels * 3;
 | 
						|
    left *= 3;
 | 
						|
    right *= 3;
 | 
						|
  }
 | 
						|
  else if(params->format == SANE_FRAME_GRAY && params->depth == 8){
 | 
						|
    pixels = right-left;
 | 
						|
    bytes = right-left;
 | 
						|
  }
 | 
						|
  else if(params->format == SANE_FRAME_GRAY && params->depth == 1){
 | 
						|
    left /= 8;
 | 
						|
    right = (right+7)/8;
 | 
						|
    bytes = right-left;
 | 
						|
    pixels = bytes * 8;
 | 
						|
  }
 | 
						|
  else{
 | 
						|
    DBG (5, "sanei_magic_crop: unsupported format/depth\n");
 | 
						|
    ret = SANE_STATUS_INVAL;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  DBG (15, "sanei_magic_crop: l:%d r:%d p:%d b:%d\n",left,right,pixels,bytes);
 | 
						|
 | 
						|
  line = malloc(bytes);
 | 
						|
  if(!line){
 | 
						|
    DBG (5, "sanei_magic_crop: no line\n");
 | 
						|
    ret = SANE_STATUS_NO_MEM;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  for(i=top; i<bot; i++){
 | 
						|
    memcpy(line, buffer + i*bwidth + left, bytes);
 | 
						|
    memcpy(buffer + pos, line, bytes);
 | 
						|
    pos += bytes;
 | 
						|
  }
 | 
						|
 | 
						|
  /* update the params struct with the new image size */
 | 
						|
  params->lines = bot-top;
 | 
						|
  params->pixels_per_line = pixels;
 | 
						|
  params->bytes_per_line = bytes;
 | 
						|
 | 
						|
  cleanup:
 | 
						|
  if(line)
 | 
						|
    free(line);
 | 
						|
 | 
						|
  DBG (10, "sanei_magic_crop: finish\n");
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
/* find angle of media rotation against image background */
 | 
						|
SANE_Status
 | 
						|
sanei_magic_findSkew(SANE_Parameters * params, SANE_Byte * buffer,
 | 
						|
  int dpiX, int dpiY, int * centerX, int * centerY, double * finSlope)
 | 
						|
{
 | 
						|
  SANE_Status ret = SANE_STATUS_GOOD;
 | 
						|
 | 
						|
  int pwidth = params->pixels_per_line;
 | 
						|
  int height = params->lines;
 | 
						|
 | 
						|
  double TSlope = 0;
 | 
						|
  int TXInter = 0;
 | 
						|
  int TYInter = 0;
 | 
						|
  double TSlopeHalf = 0;
 | 
						|
  int TOffsetHalf = 0;
 | 
						|
 | 
						|
  double LSlope = 0;
 | 
						|
  int LXInter = 0;
 | 
						|
  int LYInter = 0;
 | 
						|
  double LSlopeHalf = 0;
 | 
						|
  int LOffsetHalf = 0;
 | 
						|
 | 
						|
  int rotateX = 0;
 | 
						|
  int rotateY = 0;
 | 
						|
 | 
						|
  int * topBuf = NULL, * botBuf = NULL;
 | 
						|
 | 
						|
  DBG (10, "sanei_magic_findSkew: start\n");
 | 
						|
 | 
						|
  dpiX=dpiX;
 | 
						|
 | 
						|
  /* get buffers for edge detection */
 | 
						|
  topBuf = sanei_magic_getTransY(params,dpiY,buffer,1);
 | 
						|
  if(!topBuf){
 | 
						|
    DBG (5, "sanei_magic_findSkew: cant gTY\n");
 | 
						|
    ret = SANE_STATUS_NO_MEM;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  botBuf = sanei_magic_getTransY(params,dpiY,buffer,0);
 | 
						|
  if(!botBuf){
 | 
						|
    DBG (5, "sanei_magic_findSkew: cant gTY\n");
 | 
						|
    ret = SANE_STATUS_NO_MEM;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  /* find best top line */
 | 
						|
  ret = getTopEdge (pwidth, height, dpiY, topBuf,
 | 
						|
    &TSlope, &TXInter, &TYInter);
 | 
						|
  if(ret){
 | 
						|
    DBG(5,"sanei_magic_findSkew: gTE error: %d",ret);
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
  DBG(15,"top: %04.04f %d %d\n",TSlope,TXInter,TYInter);
 | 
						|
 | 
						|
  /* slope is too shallow, don't want to divide by 0 */
 | 
						|
  if(fabs(TSlope) < 0.0001){
 | 
						|
    DBG(15,"sanei_magic_findSkew: slope too shallow: %0.08f\n",TSlope);
 | 
						|
    ret = SANE_STATUS_UNSUPPORTED;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  /* find best left line, perpendicular to top line */
 | 
						|
  LSlope = (double)-1/TSlope;
 | 
						|
  ret = getLeftEdge (pwidth, height, topBuf, botBuf, LSlope,
 | 
						|
    &LXInter, &LYInter);
 | 
						|
  if(ret){
 | 
						|
    DBG(5,"sanei_magic_findSkew: gLE error: %d",ret);
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
  DBG(15,"sanei_magic_findSkew: left: %04.04f %d %d\n",LSlope,LXInter,LYInter);
 | 
						|
 | 
						|
  /* find point about which to rotate */
 | 
						|
  TSlopeHalf = tan(atan(TSlope)/2);
 | 
						|
  TOffsetHalf = LYInter;
 | 
						|
  DBG(15,"sanei_magic_findSkew: top half: %04.04f %d\n",TSlopeHalf,TOffsetHalf);
 | 
						|
 | 
						|
  LSlopeHalf = tan((atan(LSlope) + ((LSlope < 0)?-M_PI_2:M_PI_2))/2);
 | 
						|
  LOffsetHalf = - LSlopeHalf * TXInter;
 | 
						|
  DBG(15,"sanei_magic_findSkew: left half: %04.04f %d\n",LSlopeHalf,LOffsetHalf);
 | 
						|
 | 
						|
  rotateX = (LOffsetHalf-TOffsetHalf) / (TSlopeHalf-LSlopeHalf);
 | 
						|
  rotateY = TSlopeHalf * rotateX + TOffsetHalf;
 | 
						|
  DBG(15,"sanei_magic_findSkew: rotate: %d %d\n",rotateX,rotateY);
 | 
						|
 | 
						|
  *centerX = rotateX;
 | 
						|
  *centerY = rotateY;
 | 
						|
  *finSlope = TSlope;
 | 
						|
 | 
						|
  cleanup:
 | 
						|
  if(topBuf)
 | 
						|
    free(topBuf);
 | 
						|
  if(botBuf)
 | 
						|
    free(botBuf);
 | 
						|
 | 
						|
  DBG (10, "sanei_magic_findSkew: finish\n");
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
/* function to do a simple rotation by a given slope, around
 | 
						|
 * a given point. The point can be outside of image to get
 | 
						|
 * proper edge alignment. Unused areas filled with bg color
 | 
						|
 * FIXME: Do in-place rotation to save memory */
 | 
						|
SANE_Status
 | 
						|
sanei_magic_rotate (SANE_Parameters * params, SANE_Byte * buffer,
 | 
						|
  int centerX, int centerY, double slope, int bg_color)
 | 
						|
{
 | 
						|
 | 
						|
  SANE_Status ret = SANE_STATUS_GOOD;
 | 
						|
 | 
						|
  double slopeRad = -atan(slope);
 | 
						|
  double slopeSin = sin(slopeRad);
 | 
						|
  double slopeCos = cos(slopeRad);
 | 
						|
 | 
						|
  int pwidth = params->pixels_per_line;
 | 
						|
  int bwidth = params->bytes_per_line;
 | 
						|
  int height = params->lines;
 | 
						|
  int depth = 1;
 | 
						|
 | 
						|
  unsigned char * outbuf;
 | 
						|
  int i, j, k;
 | 
						|
 | 
						|
  DBG(10,"sanei_magic_rotate: start: %d %d\n",centerX,centerY);
 | 
						|
 | 
						|
  outbuf = malloc(bwidth*height);
 | 
						|
  if(!outbuf){
 | 
						|
    DBG(15,"sanei_magic_rotate: no outbuf\n");
 | 
						|
    ret = SANE_STATUS_NO_MEM;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  if(params->format == SANE_FRAME_RGB ||
 | 
						|
    (params->format == SANE_FRAME_GRAY && params->depth == 8)
 | 
						|
  ){
 | 
						|
 | 
						|
    if(params->format == SANE_FRAME_RGB)
 | 
						|
      depth = 3;
 | 
						|
 | 
						|
    memset(outbuf,bg_color,bwidth*height);
 | 
						|
 | 
						|
    for (i=0; i<height; i++) {
 | 
						|
      int shiftY = centerY - i;
 | 
						|
 | 
						|
      for (j=0; j<pwidth; j++) {
 | 
						|
        int shiftX = centerX - j;
 | 
						|
        int sourceX, sourceY;
 | 
						|
 | 
						|
        sourceX = centerX - (int)(shiftX * slopeCos + shiftY * slopeSin);
 | 
						|
        if (sourceX < 0 || sourceX >= pwidth)
 | 
						|
          continue;
 | 
						|
 | 
						|
        sourceY = centerY + (int)(-shiftY * slopeCos + shiftX * slopeSin);
 | 
						|
        if (sourceY < 0 || sourceY >= height)
 | 
						|
          continue;
 | 
						|
 | 
						|
        for (k=0; k<depth; k++) {
 | 
						|
          outbuf[i*bwidth+j*depth+k]
 | 
						|
            = buffer[sourceY*bwidth+sourceX*depth+k];
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  else if(params->format == SANE_FRAME_GRAY && params->depth == 1){
 | 
						|
 | 
						|
    if(bg_color)
 | 
						|
      bg_color = 0xff;
 | 
						|
 | 
						|
    memset(outbuf,bg_color,bwidth*height);
 | 
						|
 | 
						|
    for (i=0; i<height; i++) {
 | 
						|
      int shiftY = centerY - i;
 | 
						|
 | 
						|
      for (j=0; j<pwidth; j++) {
 | 
						|
        int shiftX = centerX - j;
 | 
						|
        int sourceX, sourceY;
 | 
						|
 | 
						|
        sourceX = centerX - (int)(shiftX * slopeCos + shiftY * slopeSin);
 | 
						|
        if (sourceX < 0 || sourceX >= pwidth)
 | 
						|
          continue;
 | 
						|
 | 
						|
        sourceY = centerY + (int)(-shiftY * slopeCos + shiftX * slopeSin);
 | 
						|
        if (sourceY < 0 || sourceY >= height)
 | 
						|
          continue;
 | 
						|
 | 
						|
        /* wipe out old bit */
 | 
						|
        outbuf[i*bwidth + j/8] &= ~(1 << (7-(j%8)));
 | 
						|
 | 
						|
        /* fill in new bit */
 | 
						|
        outbuf[i*bwidth + j/8] |=
 | 
						|
          ((buffer[sourceY*bwidth + sourceX/8]
 | 
						|
          >> (7-(sourceX%8))) & 1) << (7-(j%8));
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  else{
 | 
						|
    DBG (5, "sanei_magic_rotate: unsupported format/depth\n");
 | 
						|
    ret = SANE_STATUS_INVAL;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  memcpy(buffer,outbuf,bwidth*height);
 | 
						|
 | 
						|
  cleanup:
 | 
						|
 | 
						|
  if(outbuf)
 | 
						|
    free(outbuf);
 | 
						|
 | 
						|
  DBG(10,"sanei_magic_rotate: finish\n");
 | 
						|
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
SANE_Status
 | 
						|
sanei_magic_isBlank (SANE_Parameters * params, SANE_Byte * buffer,
 | 
						|
  double thresh)
 | 
						|
{
 | 
						|
  SANE_Status ret = SANE_STATUS_GOOD;
 | 
						|
  double imagesum = 0;
 | 
						|
  int i, j;
 | 
						|
 | 
						|
  DBG(10,"sanei_magic_isBlank: start: %f\n",thresh);
 | 
						|
 | 
						|
  /*convert thresh from percent (0-100) to 0-1 range*/
 | 
						|
  thresh /= 100;
 | 
						|
 | 
						|
  if(params->format == SANE_FRAME_RGB ||
 | 
						|
    (params->format == SANE_FRAME_GRAY && params->depth == 8)
 | 
						|
  ){
 | 
						|
 | 
						|
    /* loop over all rows, find density of each */
 | 
						|
    for(i=0; i<params->lines; i++){
 | 
						|
      int rowsum = 0;
 | 
						|
      SANE_Byte * ptr = buffer + params->bytes_per_line*i;
 | 
						|
 | 
						|
      /* loop over all columns, sum the 'darkness' of the pixels */
 | 
						|
      for(j=0; j<params->bytes_per_line; j++){
 | 
						|
        rowsum += 255 - ptr[j];
 | 
						|
      }
 | 
						|
 | 
						|
      imagesum += (double)rowsum/params->bytes_per_line/255;
 | 
						|
    }
 | 
						|
 | 
						|
  }
 | 
						|
  else if(params->format == SANE_FRAME_GRAY && params->depth == 1){
 | 
						|
 | 
						|
    /* loop over all rows, find density of each */
 | 
						|
    for(i=0; i<params->lines; i++){
 | 
						|
      int rowsum = 0;
 | 
						|
      SANE_Byte * ptr = buffer + params->bytes_per_line*i;
 | 
						|
 | 
						|
      /* loop over all columns, sum the pixels */
 | 
						|
      for(j=0; j<params->pixels_per_line; j++){
 | 
						|
        rowsum += ptr[j/8] >> (7-(j%8)) & 1;
 | 
						|
      }
 | 
						|
 | 
						|
      imagesum += (double)rowsum/params->pixels_per_line;
 | 
						|
    }
 | 
						|
 | 
						|
  }
 | 
						|
  else{
 | 
						|
    DBG (5, "sanei_magic_isBlank: unsupported format/depth\n");
 | 
						|
    ret = SANE_STATUS_INVAL;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  DBG (5, "sanei_magic_isBlank: sum:%f lines:%d thresh:%f density:%f\n",
 | 
						|
    imagesum,params->lines,thresh,imagesum/params->lines);
 | 
						|
 | 
						|
  if(imagesum/params->lines <= thresh){
 | 
						|
    DBG (5, "sanei_magic_isBlank: blank!\n");
 | 
						|
    ret = SANE_STATUS_NO_DOCS;
 | 
						|
  }
 | 
						|
 | 
						|
  cleanup:
 | 
						|
 | 
						|
  DBG(10,"sanei_magic_isBlank: finish\n");
 | 
						|
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
/* Divide the image into 1/2 inch squares, skipping a 1/4 inch
 | 
						|
 * margin on all sides. If all squares are under the user's density,
 | 
						|
 * signal our caller to skip the image entirely, by returning
 | 
						|
 * SANE_STATUS_NO_DOCS */
 | 
						|
SANE_Status
 | 
						|
sanei_magic_isBlank2 (SANE_Parameters * params, SANE_Byte * buffer,
 | 
						|
  int dpiX, int dpiY, double thresh)
 | 
						|
{
 | 
						|
  int xb,yb,x,y;
 | 
						|
 | 
						|
  /* .25 inch, rounded down to 8 pixel */
 | 
						|
  int xquarter = dpiX/4/8*8;
 | 
						|
  int yquarter = dpiY/4/8*8;
 | 
						|
  int xhalf    = xquarter*2;
 | 
						|
  int yhalf    = yquarter*2;
 | 
						|
  int blockpix = xhalf*yhalf;
 | 
						|
  int xblocks  = (params->pixels_per_line-xhalf)/xhalf;
 | 
						|
  int yblocks  = (params->lines-yhalf)/yhalf;
 | 
						|
 | 
						|
  /*convert thresh from percent (0-100) to 0-1 range*/
 | 
						|
  thresh /= 100;
 | 
						|
 | 
						|
  DBG (10, "sanei_magic_isBlank2: start %d %d %f %d\n",xhalf,yhalf,thresh,blockpix);
 | 
						|
 | 
						|
  if(params->depth == 8 &&
 | 
						|
    (params->format == SANE_FRAME_RGB || params->format == SANE_FRAME_GRAY)
 | 
						|
  ){
 | 
						|
 | 
						|
    int Bpp = params->format == SANE_FRAME_RGB ? 3 : 1;
 | 
						|
 | 
						|
    for(yb=0; yb<yblocks; yb++){
 | 
						|
      for(xb=0; xb<xblocks; xb++){
 | 
						|
 | 
						|
        /*count dark pix in this block*/
 | 
						|
        double blocksum = 0;
 | 
						|
 | 
						|
        for(y=0; y<yhalf; y++){
 | 
						|
 | 
						|
          /* skip the top and left 1/4 inch */
 | 
						|
          int offset = (yquarter + yb*yhalf + y) * params->bytes_per_line
 | 
						|
            + (xquarter + xb*xhalf) * Bpp;
 | 
						|
          SANE_Byte * ptr = buffer + offset;
 | 
						|
 | 
						|
          /*count darkness of pix in this row*/
 | 
						|
          int rowsum = 0;
 | 
						|
 | 
						|
          for(x=0; x<xhalf*Bpp; x++){
 | 
						|
            rowsum += 255 - ptr[x];
 | 
						|
          }
 | 
						|
 | 
						|
          blocksum += (double)rowsum/(xhalf*Bpp)/255;
 | 
						|
        }
 | 
						|
 | 
						|
        /* block was darker than thresh, keep image */
 | 
						|
        if(blocksum/yhalf > thresh){
 | 
						|
          DBG (15, "sanei_magic_isBlank2: not blank %f %d %d\n", blocksum/yhalf, yb, xb);
 | 
						|
          return SANE_STATUS_GOOD;
 | 
						|
        }
 | 
						|
        DBG (20, "sanei_magic_isBlank2: block blank %f %d %d\n", blocksum/yhalf, yb, xb);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  else if(params->format == SANE_FRAME_GRAY && params->depth == 1){
 | 
						|
 | 
						|
    for(yb=0; yb<yblocks; yb++){
 | 
						|
      for(xb=0; xb<xblocks; xb++){
 | 
						|
 | 
						|
        /*count dark pix in this block*/
 | 
						|
        double blocksum = 0;
 | 
						|
 | 
						|
        for(y=0; y<yhalf; y++){
 | 
						|
 | 
						|
          /* skip the top and left 1/4 inch */
 | 
						|
          int offset = (yquarter + yb*yhalf + y) * params->bytes_per_line
 | 
						|
            + (xquarter + xb*xhalf) / 8;
 | 
						|
          SANE_Byte * ptr = buffer + offset;
 | 
						|
 | 
						|
          /*count darkness of pix in this row*/
 | 
						|
          int rowsum = 0;
 | 
						|
 | 
						|
          for(x=0; x<xhalf; x++){
 | 
						|
            rowsum += ptr[x/8] >> (7-(x%8)) & 1;
 | 
						|
          }
 | 
						|
 | 
						|
          blocksum += (double)rowsum/xhalf;
 | 
						|
        }
 | 
						|
 | 
						|
        /* block was darker than thresh, keep image */
 | 
						|
        if(blocksum/yhalf > thresh){
 | 
						|
          DBG (15, "sanei_magic_isBlank2: not blank %f %d %d\n", blocksum/yhalf, yb, xb);
 | 
						|
          return SANE_STATUS_GOOD;
 | 
						|
        }
 | 
						|
        DBG (20, "sanei_magic_isBlank2: block blank %f %d %d\n", blocksum/yhalf, yb, xb);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  else{
 | 
						|
    DBG (5, "sanei_magic_isBlank2: unsupported format/depth\n");
 | 
						|
    return SANE_STATUS_INVAL;
 | 
						|
  }
 | 
						|
 | 
						|
  DBG (10, "sanei_magic_isBlank2: returning blank\n");
 | 
						|
  return SANE_STATUS_NO_DOCS;
 | 
						|
}
 | 
						|
 | 
						|
SANE_Status
 | 
						|
sanei_magic_findTurn(SANE_Parameters * params, SANE_Byte * buffer,
 | 
						|
  int dpiX, int dpiY, int * angle)
 | 
						|
{
 | 
						|
  SANE_Status ret = SANE_STATUS_GOOD;
 | 
						|
  int i, j, k;
 | 
						|
  int depth = 1;
 | 
						|
  int htrans=0, vtrans=0;
 | 
						|
  int htot=0, vtot=0;
 | 
						|
 | 
						|
  DBG(10,"sanei_magic_findTurn: start\n");
 | 
						|
 | 
						|
  if(params->format == SANE_FRAME_RGB ||
 | 
						|
    (params->format == SANE_FRAME_GRAY && params->depth == 8)
 | 
						|
  ){
 | 
						|
 | 
						|
    if(params->format == SANE_FRAME_RGB)
 | 
						|
      depth = 3;
 | 
						|
 | 
						|
    /* loop over some rows, count segment lengths */
 | 
						|
    for(i=0; i<params->lines; i+=dpiY/20){
 | 
						|
      SANE_Byte * ptr = buffer + params->bytes_per_line*i;
 | 
						|
      int color = 0;
 | 
						|
      int len   = 0;
 | 
						|
      int sum   = 0;
 | 
						|
 | 
						|
      /* loop over all columns */
 | 
						|
      for(j=0; j<params->pixels_per_line; j++){
 | 
						|
        int curr = 0;
 | 
						|
 | 
						|
        /*convert color to gray*/
 | 
						|
        for (k=0; k<depth; k++) {
 | 
						|
          curr += ptr[j*depth+k];
 | 
						|
        }
 | 
						|
        curr /= depth;
 | 
						|
 | 
						|
        /*convert gray to binary (with hysteresis) */
 | 
						|
        curr = (curr < 100)?1:
 | 
						|
               (curr > 156)?0:color;
 | 
						|
 | 
						|
        /*count segment length*/
 | 
						|
        if(curr != color || j==params->pixels_per_line-1){
 | 
						|
          sum += len * len/5;
 | 
						|
          len = 0;
 | 
						|
          color = curr;
 | 
						|
        }
 | 
						|
        else{
 | 
						|
          len++;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      htot++;
 | 
						|
      htrans += (double)sum/params->pixels_per_line;
 | 
						|
    }
 | 
						|
 | 
						|
    /* loop over some cols, count dark vs light transitions */
 | 
						|
    for(i=0; i<params->pixels_per_line; i+=dpiX/20){
 | 
						|
      SANE_Byte * ptr = buffer + i*depth;
 | 
						|
      int color = 0;
 | 
						|
      int len   = 0;
 | 
						|
      int sum   = 0;
 | 
						|
 | 
						|
      /* loop over all rows */
 | 
						|
      for(j=0; j<params->lines; j++){
 | 
						|
        int curr = 0;
 | 
						|
 | 
						|
        /*convert color to gray*/
 | 
						|
        for (k=0; k<depth; k++) {
 | 
						|
          curr += ptr[j*params->bytes_per_line+k];
 | 
						|
        }
 | 
						|
        curr /= depth;
 | 
						|
 | 
						|
        /*convert gray to binary (with hysteresis) */
 | 
						|
        curr = (curr < 100)?1:
 | 
						|
               (curr > 156)?0:color;
 | 
						|
 | 
						|
        /*count segment length*/
 | 
						|
        if(curr != color || j==params->lines-1){
 | 
						|
          sum += len * len/5;
 | 
						|
          len = 0;
 | 
						|
          color = curr;
 | 
						|
        }
 | 
						|
        else{
 | 
						|
          len++;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      vtot++;
 | 
						|
      vtrans += (double)sum/params->lines;
 | 
						|
    }
 | 
						|
 | 
						|
  }
 | 
						|
  else if(params->format == SANE_FRAME_GRAY && params->depth == 1){
 | 
						|
 | 
						|
    /* loop over some rows, count segment lengths */
 | 
						|
    for(i=0; i<params->lines; i+=dpiY/30){
 | 
						|
      SANE_Byte * ptr = buffer + params->bytes_per_line*i;
 | 
						|
      int color = 0;
 | 
						|
      int len   = 0;
 | 
						|
      int sum   = 0;
 | 
						|
 | 
						|
      /* loop over all columns */
 | 
						|
      for(j=0; j<params->pixels_per_line; j++){
 | 
						|
        int curr = ptr[j/8] >> (7-(j%8)) & 1;
 | 
						|
 | 
						|
        /*count segment length*/
 | 
						|
        if(curr != color || j==params->pixels_per_line-1){
 | 
						|
          sum += len * len/5;
 | 
						|
          len = 0;
 | 
						|
          color = curr;
 | 
						|
        }
 | 
						|
        else{
 | 
						|
          len++;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      htot++;
 | 
						|
      htrans += (double)sum/params->pixels_per_line;
 | 
						|
    }
 | 
						|
 | 
						|
    /* loop over some cols, count dark vs light transitions */
 | 
						|
    for(i=0; i<params->pixels_per_line; i+=dpiX/30){
 | 
						|
      SANE_Byte * ptr = buffer;
 | 
						|
      int color = 0;
 | 
						|
      int len   = 0;
 | 
						|
      int sum   = 0;
 | 
						|
 | 
						|
      /* loop over all rows */
 | 
						|
      for(j=0; j<params->lines; j++){
 | 
						|
        int curr = ptr[j*params->bytes_per_line + i/8] >> (7-(i%8)) & 1;
 | 
						|
 | 
						|
        /*count segment length*/
 | 
						|
        if(curr != color || j==params->lines-1){
 | 
						|
          sum += len * len/5;
 | 
						|
          len = 0;
 | 
						|
          color = curr;
 | 
						|
        }
 | 
						|
        else{
 | 
						|
          len++;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      vtot++;
 | 
						|
      vtrans += (double)sum/params->lines;
 | 
						|
    }
 | 
						|
 | 
						|
  }
 | 
						|
  else{
 | 
						|
    DBG (5, "sanei_magic_findTurn: unsupported format/depth\n");
 | 
						|
    ret = SANE_STATUS_INVAL;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  DBG (10, "sanei_magic_findTurn: vtrans=%d vtot=%d vfrac=%f htrans=%d htot=%d hfrac=%f\n",
 | 
						|
    vtrans, vtot, (double)vtrans/vtot, htrans, htot, (double)htrans/htot
 | 
						|
  );
 | 
						|
 | 
						|
  if((double)vtrans/vtot > (double)htrans/htot){
 | 
						|
    DBG (10, "sanei_magic_findTurn: suggest turning 90\n");
 | 
						|
    *angle = 90;
 | 
						|
  }
 | 
						|
 | 
						|
  cleanup:
 | 
						|
 | 
						|
  DBG(10,"sanei_magic_findTurn: finish\n");
 | 
						|
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
/* FIXME: Do in-place rotation to save memory */
 | 
						|
SANE_Status
 | 
						|
sanei_magic_turn(SANE_Parameters * params, SANE_Byte * buffer,
 | 
						|
  int angle)
 | 
						|
{
 | 
						|
  SANE_Status ret = SANE_STATUS_GOOD;
 | 
						|
  int opwidth, ipwidth = params->pixels_per_line;
 | 
						|
  int obwidth, ibwidth = params->bytes_per_line;
 | 
						|
  int oheight, iheight = params->lines;
 | 
						|
  int depth = 1;
 | 
						|
 | 
						|
  unsigned char * outbuf = NULL;
 | 
						|
  int i, j, k;
 | 
						|
 | 
						|
  DBG(10,"sanei_magic_turn: start %d\n",angle);
 | 
						|
 | 
						|
  if(params->format == SANE_FRAME_RGB)
 | 
						|
    depth = 3;
 | 
						|
 | 
						|
  /*clean angle and convert to 0-3*/
 | 
						|
  angle = (angle % 360) / 90;
 | 
						|
 | 
						|
  /*figure size of output image*/
 | 
						|
  switch(angle){
 | 
						|
    case 1:
 | 
						|
    case 3:
 | 
						|
      opwidth = iheight;
 | 
						|
      oheight = ipwidth;
 | 
						|
 | 
						|
      /*gray and color, 1 or 3 bytes per pixel*/
 | 
						|
      if ( params->format == SANE_FRAME_RGB
 | 
						|
        || (params->format == SANE_FRAME_GRAY && params->depth == 8)
 | 
						|
      ){
 | 
						|
        obwidth = opwidth*depth;
 | 
						|
      }
 | 
						|
 | 
						|
      /*clamp binary to byte width. must be <= input image*/
 | 
						|
      else if(params->format == SANE_FRAME_GRAY && params->depth == 1){
 | 
						|
        obwidth = opwidth/8;
 | 
						|
        opwidth = obwidth*8;
 | 
						|
      }
 | 
						|
 | 
						|
      else{
 | 
						|
        DBG(10,"sanei_magic_turn: bad params\n");
 | 
						|
        ret = SANE_STATUS_INVAL;
 | 
						|
        goto cleanup;
 | 
						|
      }
 | 
						|
 | 
						|
      break;
 | 
						|
 | 
						|
    case 2:
 | 
						|
      opwidth = ipwidth;
 | 
						|
      obwidth = ibwidth;
 | 
						|
      oheight = iheight;
 | 
						|
      break;
 | 
						|
 | 
						|
    default:
 | 
						|
      DBG(10,"sanei_magic_turn: no turn\n");
 | 
						|
      goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  /*get output image buffer*/
 | 
						|
  outbuf = malloc(obwidth*oheight);
 | 
						|
  if(!outbuf){
 | 
						|
    DBG(15,"sanei_magic_turn: no outbuf\n");
 | 
						|
    ret = SANE_STATUS_NO_MEM;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  /*turn color & gray image*/
 | 
						|
  if(params->format == SANE_FRAME_RGB ||
 | 
						|
    (params->format == SANE_FRAME_GRAY && params->depth == 8)
 | 
						|
  ){
 | 
						|
 | 
						|
    switch (angle) {
 | 
						|
 | 
						|
      /*rotate 90 clockwise*/
 | 
						|
      case 1:
 | 
						|
        for (i=0; i<oheight; i++) {
 | 
						|
          for (j=0; j<opwidth; j++) {
 | 
						|
            for (k=0; k<depth; k++) {
 | 
						|
              outbuf[i*obwidth + j*depth + k]
 | 
						|
                = buffer[(iheight-j-1)*ibwidth + i*depth + k];
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
      /*rotate 180 clockwise*/
 | 
						|
      case 2:
 | 
						|
        for (i=0; i<oheight; i++) {
 | 
						|
          for (j=0; j<opwidth; j++) {
 | 
						|
            for (k=0; k<depth; k++) {
 | 
						|
              outbuf[i*obwidth + j*depth + k]
 | 
						|
                = buffer[(iheight-i-1)*ibwidth + (ipwidth-j-1)*depth + k];
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
      /*rotate 270 clockwise*/
 | 
						|
      case 3:
 | 
						|
        for (i=0; i<oheight; i++) {
 | 
						|
          for (j=0; j<opwidth; j++) {
 | 
						|
            for (k=0; k<depth; k++) {
 | 
						|
              outbuf[i*obwidth + j*depth + k]
 | 
						|
                = buffer[j*ibwidth + (ipwidth-i-1)*depth + k];
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
    } /*end switch*/
 | 
						|
  }
 | 
						|
 | 
						|
  /*turn binary image*/
 | 
						|
  else if(params->format == SANE_FRAME_GRAY && params->depth == 1){
 | 
						|
 | 
						|
    switch (angle) {
 | 
						|
 | 
						|
      /*rotate 90 clockwise*/
 | 
						|
      case 1:
 | 
						|
        for (i=0; i<oheight; i++) {
 | 
						|
          for (j=0; j<opwidth; j++) {
 | 
						|
            unsigned char curr
 | 
						|
              = buffer[(iheight-j-1)*ibwidth + i/8] >> (7-(i%8)) & 1;
 | 
						|
 | 
						|
            unsigned char mask = 1 << (7-(j%8));
 | 
						|
 | 
						|
            if(curr){
 | 
						|
              outbuf[i*obwidth + j/8] |= mask;
 | 
						|
            }
 | 
						|
            else{
 | 
						|
              outbuf[i*obwidth + j/8] &= (~mask);
 | 
						|
            }
 | 
						|
 | 
						|
          }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
      /*rotate 180 clockwise*/
 | 
						|
      case 2:
 | 
						|
        for (i=0; i<oheight; i++) {
 | 
						|
          for (j=0; j<opwidth; j++) {
 | 
						|
            unsigned char curr
 | 
						|
              = buffer[(iheight-i-1)*ibwidth + (ipwidth-j-1)/8] >> (j%8) & 1;
 | 
						|
 | 
						|
            unsigned char mask = 1 << (7-(j%8));
 | 
						|
 | 
						|
            if(curr){
 | 
						|
              outbuf[i*obwidth + j/8] |= mask;
 | 
						|
            }
 | 
						|
            else{
 | 
						|
              outbuf[i*obwidth + j/8] &= (~mask);
 | 
						|
            }
 | 
						|
 | 
						|
          }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
      /*rotate 270 clockwise*/
 | 
						|
      case 3:
 | 
						|
        for (i=0; i<oheight; i++) {
 | 
						|
          for (j=0; j<opwidth; j++) {
 | 
						|
            unsigned char curr
 | 
						|
              = buffer[j*ibwidth + (ipwidth-i-1)/8] >> (i%8) & 1;
 | 
						|
 | 
						|
            unsigned char mask = 1 << (7-(j%8));
 | 
						|
 | 
						|
            if(curr){
 | 
						|
              outbuf[i*obwidth + j/8] |= mask;
 | 
						|
            }
 | 
						|
            else{
 | 
						|
              outbuf[i*obwidth + j/8] &= (~mask);
 | 
						|
            }
 | 
						|
 | 
						|
          }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
    } /*end switch*/
 | 
						|
  }
 | 
						|
 | 
						|
  else{
 | 
						|
    DBG (5, "sanei_magic_turn: unsupported format/depth\n");
 | 
						|
    ret = SANE_STATUS_INVAL;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  /*copy output back into input buffer*/
 | 
						|
  memcpy(buffer,outbuf,obwidth*oheight);
 | 
						|
 | 
						|
  /*update input params*/
 | 
						|
  params->pixels_per_line = opwidth;
 | 
						|
  params->bytes_per_line = obwidth;
 | 
						|
  params->lines = oheight;
 | 
						|
 | 
						|
  cleanup:
 | 
						|
 | 
						|
  if(outbuf)
 | 
						|
    free(outbuf);
 | 
						|
 | 
						|
  DBG(10,"sanei_magic_turn: finish\n");
 | 
						|
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
/* Utility functions, not used outside this file */
 | 
						|
 | 
						|
/* Repeatedly call getLine to find the best range of slope and offset.
 | 
						|
 * Shift the ranges thru 4 different positions to avoid splitting data
 | 
						|
 * across multiple bins (false positive). Home-in on the most likely upper
 | 
						|
 * line of the paper inside the image. Return the 'best' edge. */
 | 
						|
static SANE_Status
 | 
						|
getTopEdge(int width, int height, int resolution,
 | 
						|
  int * buff, double * finSlope, int * finXInter, int * finYInter)
 | 
						|
{
 | 
						|
  SANE_Status ret = SANE_STATUS_GOOD;
 | 
						|
 | 
						|
  int slopes = 31;
 | 
						|
  int offsets = 31;
 | 
						|
  double maxSlope = 1;
 | 
						|
  double minSlope = -1;
 | 
						|
  int maxOffset = resolution;
 | 
						|
  int minOffset = -resolution;
 | 
						|
 | 
						|
  double topSlope = 0;
 | 
						|
  int topOffset = 0;
 | 
						|
  int topDensity = 0;
 | 
						|
 | 
						|
  int i,j;
 | 
						|
  int pass = 0;
 | 
						|
 | 
						|
  DBG(10,"getTopEdge: start\n");
 | 
						|
 | 
						|
  while(pass++ < 7){
 | 
						|
    double sStep = (maxSlope-minSlope)/slopes;
 | 
						|
    int oStep = (maxOffset-minOffset)/offsets;
 | 
						|
 | 
						|
    double slope = 0;
 | 
						|
    int offset = 0;
 | 
						|
    int density = 0;
 | 
						|
    int go = 0;
 | 
						|
 | 
						|
    topSlope = 0;
 | 
						|
    topOffset = 0;
 | 
						|
    topDensity = 0;
 | 
						|
 | 
						|
    /* find lines 4 times with slightly moved params,
 | 
						|
     * to bypass binning errors, highest density wins */
 | 
						|
    for(i=0;i<2;i++){
 | 
						|
      double sStep2 = sStep*i/2;
 | 
						|
      for(j=0;j<2;j++){
 | 
						|
        int oStep2 = oStep*j/2;
 | 
						|
        ret = getLine(height,width,buff,slopes,minSlope+sStep2,maxSlope+sStep2,offsets,minOffset+oStep2,maxOffset+oStep2,&slope,&offset,&density);
 | 
						|
        if(ret){
 | 
						|
          DBG(5,"getTopEdge: getLine error %d\n",ret);
 | 
						|
          return ret;
 | 
						|
        }
 | 
						|
        DBG(15,"getTopEdge: %d %d %+0.4f %d %d\n",i,j,slope,offset,density);
 | 
						|
 | 
						|
        if(density > topDensity){
 | 
						|
          topSlope = slope;
 | 
						|
          topOffset = offset;
 | 
						|
          topDensity = density;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    DBG(15,"getTopEdge: ok %+0.4f %d %d\n",topSlope,topOffset,topDensity);
 | 
						|
 | 
						|
    /* did not find anything promising on first pass,
 | 
						|
     * give up instead of fixating on some small, pointless feature */
 | 
						|
    if(pass == 1 && topDensity < width/5){
 | 
						|
      DBG(5,"getTopEdge: density too small %d %d\n",topDensity,width);
 | 
						|
      topOffset = 0;
 | 
						|
      topSlope = 0;
 | 
						|
      break;
 | 
						|
    }
 | 
						|
 | 
						|
    /* if slope can zoom in some more, do so. */
 | 
						|
    if(sStep >= 0.0001){
 | 
						|
      minSlope = topSlope - sStep;
 | 
						|
      maxSlope = topSlope + sStep;
 | 
						|
      go = 1;
 | 
						|
    }
 | 
						|
 | 
						|
    /* if offset can zoom in some more, do so. */
 | 
						|
    if(oStep){
 | 
						|
      minOffset = topOffset - oStep;
 | 
						|
      maxOffset = topOffset + oStep;
 | 
						|
      go = 1;
 | 
						|
    }
 | 
						|
 | 
						|
    /* cannot zoom in more, bail out */
 | 
						|
    if(!go){
 | 
						|
      break;
 | 
						|
    }
 | 
						|
 | 
						|
    DBG(15,"getTopEdge: zoom: %+0.4f %+0.4f %d %d\n",
 | 
						|
      minSlope,maxSlope,minOffset,maxOffset);
 | 
						|
  }
 | 
						|
 | 
						|
  /* topOffset is in the center of the image,
 | 
						|
   * convert to x and y intercept */
 | 
						|
  if(topSlope != 0){
 | 
						|
    *finYInter = topOffset - topSlope * width/2;
 | 
						|
    *finXInter = *finYInter / -topSlope;
 | 
						|
    *finSlope = topSlope;
 | 
						|
  }
 | 
						|
  else{
 | 
						|
    *finYInter = 0;
 | 
						|
    *finXInter = 0;
 | 
						|
    *finSlope = 0;
 | 
						|
  }
 | 
						|
 | 
						|
  DBG(10,"getTopEdge: finish\n");
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
/* Loop thru a transition array, and use a simplified Hough transform
 | 
						|
 * to divide likely edges into a 2-d array of bins. Then weight each
 | 
						|
 * bin based on its angle and offset. Return the 'best' bin. */
 | 
						|
static SANE_Status
 | 
						|
getLine (int height, int width, int * buff,
 | 
						|
  int slopes, double minSlope, double maxSlope,
 | 
						|
  int offsets, int minOffset, int maxOffset,
 | 
						|
  double * finSlope, int * finOffset, int * finDensity)
 | 
						|
{
 | 
						|
  SANE_Status ret = 0;
 | 
						|
 | 
						|
  int ** lines = NULL;
 | 
						|
  int i, j;
 | 
						|
  int rise, run;
 | 
						|
  double slope;
 | 
						|
  int offset;
 | 
						|
  int sIndex, oIndex;
 | 
						|
  int hWidth = width/2;
 | 
						|
 | 
						|
  double * slopeCenter = NULL;
 | 
						|
  int * slopeScale = NULL;
 | 
						|
  double * offsetCenter = NULL;
 | 
						|
  int * offsetScale = NULL;
 | 
						|
 | 
						|
  int maxDensity = 1;
 | 
						|
  double absMaxSlope = fabs(maxSlope);
 | 
						|
  double absMinSlope = fabs(minSlope);
 | 
						|
  int absMaxOffset = abs(maxOffset);
 | 
						|
  int absMinOffset = abs(minOffset);
 | 
						|
 | 
						|
  DBG(10,"getLine: start %+0.4f %+0.4f %d %d\n",
 | 
						|
    minSlope,maxSlope,minOffset,maxOffset);
 | 
						|
 | 
						|
  /*silence compiler*/
 | 
						|
  height = height;
 | 
						|
 | 
						|
  if(absMaxSlope < absMinSlope)
 | 
						|
    absMaxSlope = absMinSlope;
 | 
						|
 | 
						|
  if(absMaxOffset < absMinOffset)
 | 
						|
    absMaxOffset = absMinOffset;
 | 
						|
 | 
						|
  /* build an array of pretty-print values for slope */
 | 
						|
  slopeCenter = calloc(slopes,sizeof(double));
 | 
						|
  if(!slopeCenter){
 | 
						|
    DBG(5,"getLine: cant load slopeCenter\n");
 | 
						|
    ret = SANE_STATUS_NO_MEM;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  /* build an array of scaling factors for slope */
 | 
						|
  slopeScale = calloc(slopes,sizeof(int));
 | 
						|
  if(!slopeScale){
 | 
						|
    DBG(5,"getLine: cant load slopeScale\n");
 | 
						|
    ret = SANE_STATUS_NO_MEM;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  for(j=0;j<slopes;j++){
 | 
						|
 | 
						|
    /* find central value of this 'bucket' */
 | 
						|
    slopeCenter[j] = (
 | 
						|
      (double)j*(maxSlope-minSlope)/slopes+minSlope
 | 
						|
      + (double)(j+1)*(maxSlope-minSlope)/slopes+minSlope
 | 
						|
    )/2;
 | 
						|
 | 
						|
    /* scale value from the requested range into an inverted 100-1 range
 | 
						|
     * input close to 0 makes output close to 100 */
 | 
						|
    slopeScale[j] = 101 - fabs(slopeCenter[j])*100/absMaxSlope;
 | 
						|
  }
 | 
						|
 | 
						|
  /* build an array of pretty-print values for offset */
 | 
						|
  offsetCenter = calloc(offsets,sizeof(double));
 | 
						|
  if(!offsetCenter){
 | 
						|
    DBG(5,"getLine: cant load offsetCenter\n");
 | 
						|
    ret = SANE_STATUS_NO_MEM;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  /* build an array of scaling factors for offset */
 | 
						|
  offsetScale = calloc(offsets,sizeof(int));
 | 
						|
  if(!offsetScale){
 | 
						|
    DBG(5,"getLine: cant load offsetScale\n");
 | 
						|
    ret = SANE_STATUS_NO_MEM;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  for(j=0;j<offsets;j++){
 | 
						|
 | 
						|
    /* find central value of this 'bucket'*/
 | 
						|
    offsetCenter[j] = (
 | 
						|
      (double)j/offsets*(maxOffset-minOffset)+minOffset
 | 
						|
      + (double)(j+1)/offsets*(maxOffset-minOffset)+minOffset
 | 
						|
    )/2;
 | 
						|
 | 
						|
    /* scale value from the requested range into an inverted 100-1 range
 | 
						|
     * input close to 0 makes output close to 100 */
 | 
						|
    offsetScale[j] = 101 - fabs(offsetCenter[j])*100/absMaxOffset;
 | 
						|
  }
 | 
						|
 | 
						|
  /* build 2-d array of 'density', divided into slope and offset ranges */
 | 
						|
  lines = calloc(slopes, sizeof(int *));
 | 
						|
  if(!lines){
 | 
						|
    DBG(5,"getLine: cant load lines\n");
 | 
						|
    ret = SANE_STATUS_NO_MEM;
 | 
						|
    goto cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  for(i=0;i<slopes;i++){
 | 
						|
    if(!(lines[i] = calloc(offsets, sizeof(int)))){
 | 
						|
      DBG(5,"getLine: cant load lines %d\n",i);
 | 
						|
      ret = SANE_STATUS_NO_MEM;
 | 
						|
      goto cleanup;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  for(i=0;i<width;i++){
 | 
						|
    for(j=i+1;j<width && j<i+width/3;j++){
 | 
						|
 | 
						|
      /*FIXME: check for invalid (min/max) values?*/
 | 
						|
      rise = buff[j] - buff[i];
 | 
						|
      run = j-i;
 | 
						|
 | 
						|
      slope = (double)rise/run;
 | 
						|
      if(slope >= maxSlope || slope < minSlope)
 | 
						|
        continue;
 | 
						|
 | 
						|
      /* offset in center of width, not y intercept! */
 | 
						|
      offset = slope * hWidth + buff[i] - slope * i;
 | 
						|
      if(offset >= maxOffset || offset < minOffset)
 | 
						|
        continue;
 | 
						|
 | 
						|
      sIndex = (slope - minSlope) * slopes/(maxSlope-minSlope);
 | 
						|
      if(sIndex >= slopes)
 | 
						|
        continue;
 | 
						|
 | 
						|
      oIndex = (offset - minOffset) * offsets/(maxOffset-minOffset);
 | 
						|
      if(oIndex >= offsets)
 | 
						|
        continue;
 | 
						|
 | 
						|
      lines[sIndex][oIndex]++;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /* go thru array, and find most dense line (highest number) */
 | 
						|
  for(i=0;i<slopes;i++){
 | 
						|
    for(j=0;j<offsets;j++){
 | 
						|
      if(lines[i][j] > maxDensity)
 | 
						|
        maxDensity = lines[i][j];
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  DBG(15,"getLine: maxDensity %d\n",maxDensity);
 | 
						|
 | 
						|
  *finSlope = 0;
 | 
						|
  *finOffset = 0;
 | 
						|
  *finDensity = 0;
 | 
						|
 | 
						|
  /* go thru array, and scale densities to % of maximum, plus adjust for
 | 
						|
   * prefered (smaller absolute value) slope and offset */
 | 
						|
  for(i=0;i<slopes;i++){
 | 
						|
    for(j=0;j<offsets;j++){
 | 
						|
      lines[i][j] = (float)lines[i][j] / maxDensity * slopeScale[i] * offsetScale[j];
 | 
						|
      if(lines[i][j] > *finDensity){
 | 
						|
        *finDensity = lines[i][j];
 | 
						|
        *finSlope = slopeCenter[i];
 | 
						|
        *finOffset = offsetCenter[j];
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if(0){
 | 
						|
    fprintf(stderr,"offsetCenter:       ");
 | 
						|
    for(j=0;j<offsets;j++){
 | 
						|
      fprintf(stderr," %+04.0f",offsetCenter[j]);
 | 
						|
    }
 | 
						|
    fprintf(stderr,"\n");
 | 
						|
 | 
						|
    fprintf(stderr,"offsetScale:        ");
 | 
						|
    for(j=0;j<offsets;j++){
 | 
						|
      fprintf(stderr," %04d",offsetScale[j]);
 | 
						|
    }
 | 
						|
    fprintf(stderr,"\n");
 | 
						|
 | 
						|
    for(i=0;i<slopes;i++){
 | 
						|
      fprintf(stderr,"slope: %02d %+02.2f %03d:",i,slopeCenter[i],slopeScale[i]);
 | 
						|
      for(j=0;j<offsets;j++){
 | 
						|
        fprintf(stderr,"% 5d",lines[i][j]);
 | 
						|
      }
 | 
						|
      fprintf(stderr,"\n");
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /* dont forget to cleanup */
 | 
						|
  cleanup:
 | 
						|
  for(i=0;i<slopes;i++){
 | 
						|
    if(lines[i])
 | 
						|
      free(lines[i]);
 | 
						|
  }
 | 
						|
  if(lines)
 | 
						|
    free(lines);
 | 
						|
  if(slopeCenter)
 | 
						|
    free(slopeCenter);
 | 
						|
  if(slopeScale)
 | 
						|
    free(slopeScale);
 | 
						|
  if(offsetCenter)
 | 
						|
    free(offsetCenter);
 | 
						|
  if(offsetScale)
 | 
						|
    free(offsetScale);
 | 
						|
 | 
						|
  DBG(10,"getLine: finish\n");
 | 
						|
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
/* find the left side of paper by moving a line
 | 
						|
 * perpendicular to top slope across the image
 | 
						|
 * the 'left-most' point on the paper is the
 | 
						|
 * one with the smallest X intercept
 | 
						|
 * return x and y intercepts */
 | 
						|
static SANE_Status
 | 
						|
getLeftEdge (int width, int height, int * top, int * bot,
 | 
						|
 double slope, int * finXInter, int * finYInter)
 | 
						|
{
 | 
						|
 | 
						|
  int i;
 | 
						|
  int topXInter, topYInter;
 | 
						|
  int botXInter, botYInter;
 | 
						|
  int leftCount;
 | 
						|
 | 
						|
  DBG(10,"getEdgeSlope: start\n");
 | 
						|
 | 
						|
  topXInter = width;
 | 
						|
  topYInter = 0;
 | 
						|
  leftCount = 0;
 | 
						|
 | 
						|
  for(i=0;i<width;i++){
 | 
						|
 | 
						|
    if(top[i] < height){
 | 
						|
      int tyi = top[i] - (slope * i);
 | 
						|
      int txi = tyi/-slope;
 | 
						|
 | 
						|
      if(topXInter > txi){
 | 
						|
        topXInter = txi;
 | 
						|
        topYInter = tyi;
 | 
						|
      }
 | 
						|
 | 
						|
      leftCount++;
 | 
						|
      if(leftCount > 5){
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    else{
 | 
						|
      topXInter = width;
 | 
						|
      topYInter = 0;
 | 
						|
      leftCount = 0;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  botXInter = width;
 | 
						|
  botYInter = 0;
 | 
						|
  leftCount = 0;
 | 
						|
 | 
						|
  for(i=0;i<width;i++){
 | 
						|
 | 
						|
    if(bot[i] > -1){
 | 
						|
 | 
						|
      int byi = bot[i] - (slope * i);
 | 
						|
      int bxi = byi/-slope;
 | 
						|
 | 
						|
      if(botXInter > bxi){
 | 
						|
        botXInter = bxi;
 | 
						|
        botYInter = byi;
 | 
						|
      }
 | 
						|
 | 
						|
      leftCount++;
 | 
						|
      if(leftCount > 5){
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    else{
 | 
						|
      botXInter = width;
 | 
						|
      botYInter = 0;
 | 
						|
      leftCount = 0;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if(botXInter < topXInter){
 | 
						|
    *finXInter = botXInter;
 | 
						|
    *finYInter = botYInter;
 | 
						|
  }
 | 
						|
  else{
 | 
						|
    *finXInter = topXInter;
 | 
						|
    *finYInter = topYInter;
 | 
						|
  }
 | 
						|
 | 
						|
  DBG(10,"getEdgeSlope: finish\n");
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
/* Loop thru the image and look for first color change in each column.
 | 
						|
 * Return a malloc'd array. Caller is responsible for freeing. */
 | 
						|
int *
 | 
						|
sanei_magic_getTransY (
 | 
						|
  SANE_Parameters * params, int dpi, SANE_Byte * buffer, int top)
 | 
						|
{
 | 
						|
  int * buff;
 | 
						|
 | 
						|
  int i, j, k;
 | 
						|
  int winLen = 9;
 | 
						|
 | 
						|
  int width = params->pixels_per_line;
 | 
						|
  int height = params->lines;
 | 
						|
  int depth = 1;
 | 
						|
 | 
						|
  /* defaults for bottom-up */
 | 
						|
  int firstLine = height-1;
 | 
						|
  int lastLine = -1;
 | 
						|
  int direction = -1;
 | 
						|
 | 
						|
  DBG (10, "sanei_magic_getTransY: start\n");
 | 
						|
 | 
						|
  /* override for top-down */
 | 
						|
  if(top){
 | 
						|
    firstLine = 0;
 | 
						|
    lastLine = height;
 | 
						|
    direction = 1;
 | 
						|
  }
 | 
						|
 | 
						|
  /* build output and preload with impossible value */
 | 
						|
  buff = calloc(width,sizeof(int));
 | 
						|
  if(!buff){
 | 
						|
    DBG (5, "sanei_magic_getTransY: no buff\n");
 | 
						|
    return NULL;
 | 
						|
  }
 | 
						|
  for(i=0; i<width; i++)
 | 
						|
    buff[i] = lastLine;
 | 
						|
 | 
						|
  /* load the buff array with y value for first color change from edge
 | 
						|
   * gray/color uses a different algo from binary/halftone */
 | 
						|
  if(params->format == SANE_FRAME_RGB ||
 | 
						|
    (params->format == SANE_FRAME_GRAY && params->depth == 8)
 | 
						|
  ){
 | 
						|
 | 
						|
    if(params->format == SANE_FRAME_RGB)
 | 
						|
      depth = 3;
 | 
						|
 | 
						|
    /* loop over all columns, find first transition */
 | 
						|
    for(i=0; i<width; i++){
 | 
						|
 | 
						|
      int near = 0;
 | 
						|
      int far = 0;
 | 
						|
 | 
						|
      /* load the near and far windows with repeated copy of first pixel */
 | 
						|
      for(k=0; k<depth; k++){
 | 
						|
        near += buffer[(firstLine*width+i) * depth + k];
 | 
						|
      }
 | 
						|
      near *= winLen;
 | 
						|
      far = near;
 | 
						|
 | 
						|
      /* move windows, check delta */
 | 
						|
      for(j=firstLine+direction; j!=lastLine; j+=direction){
 | 
						|
 | 
						|
        int farLine = j-winLen*2*direction;
 | 
						|
        int nearLine = j-winLen*direction;
 | 
						|
 | 
						|
        if(farLine < 0 || farLine >= height){
 | 
						|
          farLine = firstLine;
 | 
						|
        }
 | 
						|
        if(nearLine < 0 || nearLine >= height){
 | 
						|
          nearLine = firstLine;
 | 
						|
        }
 | 
						|
 | 
						|
        for(k=0; k<depth; k++){
 | 
						|
          far -= buffer[(farLine*width+i)*depth+k];
 | 
						|
          far += buffer[(nearLine*width+i)*depth+k];
 | 
						|
 | 
						|
          near -= buffer[(nearLine*width+i)*depth+k];
 | 
						|
          near += buffer[(j*width+i)*depth+k];
 | 
						|
        }
 | 
						|
 | 
						|
        /* significant transition */
 | 
						|
        if(abs(near - far) > 50*winLen*depth - near*40/255){
 | 
						|
          buff[i] = j;
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  else if(params->format == SANE_FRAME_GRAY && params->depth == 1){
 | 
						|
 | 
						|
    int near = 0;
 | 
						|
 | 
						|
    for(i=0; i<width; i++){
 | 
						|
 | 
						|
      /* load the near window with first pixel */
 | 
						|
      near = buffer[(firstLine*width+i)/8] >> (7-(i%8)) & 1;
 | 
						|
 | 
						|
      /* move */
 | 
						|
      for(j=firstLine+direction; j!=lastLine; j+=direction){
 | 
						|
        if((buffer[(j*width+i)/8] >> (7-(i%8)) & 1) != near){
 | 
						|
          buff[i] = j;
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /* some other format? */
 | 
						|
  else{
 | 
						|
    DBG (5, "sanei_magic_getTransY: unsupported format/depth\n");
 | 
						|
    free(buff);
 | 
						|
    return NULL;
 | 
						|
  }
 | 
						|
 | 
						|
  /* ignore transitions with few neighbors within .5 inch */
 | 
						|
  for(i=0;i<width-7;i++){
 | 
						|
    int sum = 0;
 | 
						|
    for(j=1;j<=7;j++){
 | 
						|
      if(abs(buff[i+j] - buff[i]) < dpi/2)
 | 
						|
        sum++;
 | 
						|
    }
 | 
						|
    if(sum < 2)
 | 
						|
      buff[i] = lastLine;
 | 
						|
  }
 | 
						|
 | 
						|
  DBG (10, "sanei_magic_getTransY: finish\n");
 | 
						|
 | 
						|
  return buff;
 | 
						|
}
 | 
						|
 | 
						|
/* Loop thru the image height and look for first color change in each row.
 | 
						|
 * Return a malloc'd array. Caller is responsible for freeing. */
 | 
						|
int *
 | 
						|
sanei_magic_getTransX (
 | 
						|
  SANE_Parameters * params, int dpi, SANE_Byte * buffer, int left)
 | 
						|
{
 | 
						|
  int * buff;
 | 
						|
 | 
						|
  int i, j, k;
 | 
						|
  int winLen = 9;
 | 
						|
 | 
						|
  int bwidth = params->bytes_per_line;
 | 
						|
  int width = params->pixels_per_line;
 | 
						|
  int height = params->lines;
 | 
						|
  int depth = 1;
 | 
						|
 | 
						|
  /* defaults for right-first */
 | 
						|
  int firstCol = width-1;
 | 
						|
  int lastCol = -1;
 | 
						|
  int direction = -1;
 | 
						|
 | 
						|
  DBG (10, "sanei_magic_getTransX: start\n");
 | 
						|
 | 
						|
  /* override for left-first*/
 | 
						|
  if(left){
 | 
						|
    firstCol = 0;
 | 
						|
    lastCol = width;
 | 
						|
    direction = 1;
 | 
						|
  }
 | 
						|
 | 
						|
  /* build output and preload with impossible value */
 | 
						|
  buff = calloc(height,sizeof(int));
 | 
						|
  if(!buff){
 | 
						|
    DBG (5, "sanei_magic_getTransX: no buff\n");
 | 
						|
    return NULL;
 | 
						|
  }
 | 
						|
  for(i=0; i<height; i++)
 | 
						|
    buff[i] = lastCol;
 | 
						|
 | 
						|
  /* load the buff array with x value for first color change from edge
 | 
						|
   * gray/color uses a different algo from binary/halftone */
 | 
						|
  if(params->format == SANE_FRAME_RGB ||
 | 
						|
    (params->format == SANE_FRAME_GRAY && params->depth == 8)
 | 
						|
  ){
 | 
						|
 | 
						|
    if(params->format == SANE_FRAME_RGB)
 | 
						|
      depth = 3;
 | 
						|
 | 
						|
    /* loop over all columns, find first transition */
 | 
						|
    for(i=0; i<height; i++){
 | 
						|
 | 
						|
      int near = 0;
 | 
						|
      int far = 0;
 | 
						|
 | 
						|
      /* load the near and far windows with repeated copy of first pixel */
 | 
						|
      for(k=0; k<depth; k++){
 | 
						|
        near += buffer[i*bwidth + k];
 | 
						|
      }
 | 
						|
      near *= winLen;
 | 
						|
      far = near;
 | 
						|
 | 
						|
      /* move windows, check delta */
 | 
						|
      for(j=firstCol+direction; j!=lastCol; j+=direction){
 | 
						|
 | 
						|
        int farCol = j-winLen*2*direction;
 | 
						|
        int nearCol = j-winLen*direction;
 | 
						|
 | 
						|
        if(farCol < 0 || farCol >= width){
 | 
						|
          farCol = firstCol;
 | 
						|
        }
 | 
						|
        if(nearCol < 0 || nearCol >= width){
 | 
						|
          nearCol = firstCol;
 | 
						|
        }
 | 
						|
 | 
						|
        for(k=0; k<depth; k++){
 | 
						|
          far -= buffer[i*bwidth + farCol*depth + k];
 | 
						|
          far += buffer[i*bwidth + nearCol*depth + k];
 | 
						|
 | 
						|
          near -= buffer[i*bwidth + nearCol*depth + k];
 | 
						|
          near += buffer[i*bwidth + j*depth + k];
 | 
						|
        }
 | 
						|
 | 
						|
        if(abs(near - far) > 50*winLen*depth - near*40/255){
 | 
						|
          buff[i] = j;
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  else if (params->format == SANE_FRAME_GRAY && params->depth == 1){
 | 
						|
 | 
						|
    int near = 0;
 | 
						|
 | 
						|
    for(i=0; i<height; i++){
 | 
						|
 | 
						|
      /* load the near window with first pixel */
 | 
						|
      near = buffer[i*bwidth + firstCol/8] >> (7-(firstCol%8)) & 1;
 | 
						|
 | 
						|
      /* move */
 | 
						|
      for(j=firstCol+direction; j!=lastCol; j+=direction){
 | 
						|
        if((buffer[i*bwidth + j/8] >> (7-(j%8)) & 1) != near){
 | 
						|
          buff[i] = j;
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /* some other format? */
 | 
						|
  else{
 | 
						|
    DBG (5, "sanei_magic_getTransX: unsupported format/depth\n");
 | 
						|
    free(buff);
 | 
						|
    return NULL;
 | 
						|
  }
 | 
						|
 | 
						|
  /* ignore transitions with few neighbors within .5 inch */
 | 
						|
  for(i=0;i<height-7;i++){
 | 
						|
    int sum = 0;
 | 
						|
    for(j=1;j<=7;j++){
 | 
						|
      if(abs(buff[i+j] - buff[i]) < dpi/2)
 | 
						|
        sum++;
 | 
						|
    }
 | 
						|
    if(sum < 2)
 | 
						|
      buff[i] = lastCol;
 | 
						|
  }
 | 
						|
 | 
						|
  DBG (10, "sanei_magic_getTransX: finish\n");
 | 
						|
 | 
						|
  return buff;
 | 
						|
}
 |