0

I'm working on an optical character recognition school project.

At this step, I have to draw a character then apply Freeman Chain Code to it.

I'm drawing on a canvas using sketch.js

Front end code is handled with angularjs, back end with Spring & BufferedImage

Based on those two tutorials :

For canvas drawing : https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas#Grayscaling_and_inverting_colors

For sketch.js : https://intridea.github.io/sketch.js/docs/sketch.html

I wrote this client code :

...
    <div class="container">
        <div ng-controller="ctrl">
            <canvas ng-model="canvas" ng-click="enableSave()" class="panel panel-default" width="200" height="200"></canvas>
            <form class="form-horizontal" enctype="multipart/form-data">
                <div class="input-group col-md-4">
                    <span class="input-group-btn">
                        <button ng-click="clear()" id="clear" type="button" class="btn btn-default">Clear</button>
                    </span>
                    <input ng-model="num" type="number" id="num" class="form-control" type="text">
                    <span class="input-group-btn">
                        <button ng-click="save()" ng-disabled="modif == false" type="button" class="btn btn-success">Save</button>
                    </span>
                </div>
            </form>
        </div>
    </div>
    <script type="text/javascript" src="assets/js/jquery-2.2.0.min.js"></script>
    <script type="text/javascript" src="assets/js/sketch.min.js"></script>
    <script type="text/javascript" src="assets/js/angular.min.js"></script>
    <script type="text/javascript">
        var app = angular.module('app', []);
        app.controller('ctrl', ['$scope', '$log', '$http', function($scope, $log, $http) {
            // init
            $scope.modif = false;
            $scope.canvas = angular.element('canvas');
            $scope.context = $scope.canvas[0].getContext('2d');
            $scope.canvas.sketch({
                defaultColor: "#000000",
                defaultSize: 2
            });

            // enable save button if user draw on canvas
            $scope.enableSave = function() {
                $scope.modif = true;
            };
            // convert image to blob : https://stackoverflow.com/questions/4998908/convert-data-uri-to-file-then-append-to-formdata
            $scope.toBlob = function(dataURI) {
                var byteString;
                if (dataURI.split(',')[0].indexOf('base64') >= 0)
                    byteString = atob(dataURI.split(',')[1]);
                else
                    byteString = unescape(dataURI.split(',')[1]);
                var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
                var ia = new Uint8Array(byteString.length);
                for (var i = 0; i < byteString.length; i++)
                    ia[i] = byteString.charCodeAt(i);
                return new Blob([ia], {
                    type: mimeString
                });
            };
            // send user's input to server
            $scope.save = function() {
                var img = $scope.canvas[0].toDataURL('image/png');
                $log.info(img);
                var fd = new FormData();
                var blob = $scope.toBlob(img);
                fd.append('img', blob);
                $http.post('add.html', fd, {
                        withCredentials: true,
                        headers: {
                            'Content-Type': undefined
                        },
                        transformRequest: angular.identity
                    })
                    .success(function(result) {
                        $log.info(result);
                    })
                    .error(function(data, status) {});
                // clear canvas
                img = null;
                $scope.clear();
                // disable save button
                $scope.modif = false;
            };
            // clear canvas
            $scope.clear = function() {
                $scope.context.clearRect(0, 0, $scope.canvas[0].width, $scope.canvas[0].height);
                $scope.canvas.sketch('actions', []);
            }
        }]);
        $(document).ready(function() {});
    </script>
</body>

</html>

Spring controller :

@RequestMapping(value = "/add", method = RequestMethod.POST)
@ResponseBody
public String add(@RequestParam("img") MultipartFile image) throws IOException {
    log.info("add : POST");

    log.info("bytes image : " + image.getBytes().length);

    BufferedImage original = ImageIO.read(image.getInputStream());
    BufferedImage non_transparent = new BufferedImage(original.getWidth(), original.getHeight(),
            BufferedImage.TYPE_INT_RGB);
    BufferedImage black_white = new BufferedImage(non_transparent.getWidth(), non_transparent.getHeight(),
            BufferedImage.TYPE_BYTE_BINARY);

    non_transparent.getGraphics().drawImage(original, 0, 0, null);
    non_transparent.getGraphics().dispose();

    black_white.getGraphics().drawImage(non_transparent, 0, 0, null);
    black_white.getGraphics().dispose();

    byte[] original_pixels = ((DataBufferByte) original.getRaster().getDataBuffer()).getData();
    log.info("original pixels : " + original_pixels.length);

    int[] non_transparent_pixels = ((DataBufferInt) non_transparent.getRaster().getDataBuffer()).getData();
    log.info("non transparent pixels : " + non_transparent_pixels.length);

    byte[] black_white_pixels = ((DataBufferByte) black_white.getRaster().getDataBuffer()).getData();
    log.info("black white pixels : " + black_white_pixels.length);

    FileOutputStream fos_original = new FileOutputStream("original.txt");
    fos_original.write(original_pixels);
    fos_original.close();
    File f_original = new File("original.png");
    ImageIO.write(original, "png", f_original);

    File f_non_transparent = new File("non_transparent.png");
    ImageIO.write(non_transparent, "png", f_non_transparent);

    FileOutputStream fos_black_white = new FileOutputStream("black_white.txt");
    fos_black_white.write(black_white_pixels);
    fos_black_white.close();
    File f_black_white = new File("black_white.png");
    ImageIO.write(black_white, "png", f_black_white);
    return STATUS;
}

Whatever picture format I choose in var img = $scope.canvas[0].toDataURL('image/png'); the picture is always sent all transparent except the drawn on pixels.

I want to transform the sent picture from rgba to a basic (0 for white pixel, 1 for anything else) matrix.

I started testing with BufferedImage class to do this transformation, because i've read that you could do it by creating a new BufferedImage with an image type of TYPE_BYTE_BINARY from the original BufferedImage

But this method always produces a totally black image with all bytes set to 0.

So i have two question ? Is there a way to prevent transparency on the canvas ? & Is there a built in way to do rgba to 0/1 matrix transformation ?

Thanks.

7mza
  • 5
  • 4

1 Answers1

0

For question 1 :

Is there a way to prevent transparency on the canvas ?

There are several actually.

  • You can pass an {alpha: false} object to the canvas.getContext('2d') method.
    This will disable the default transparency of the context, setting it to a black rectangle. Note that it won't disable transparency of further drawn objects (ctx.globalAlpha and rgba colors will still be honored, they will simply be calculated over the black background). This is because by default, transparency on canvas is rgba(0,0,0,0), so removing the alpha value just leave rgb(0,0,0).

  • An other solution, is to fill a rectangle, which will act as the background, in the solid color you want.

var img = new Image();
img.onload = function(){

  //_____OPTION 1______//

  // get the #noAlpha canvas' 2d context, with the alpha parameter set to false
  var naCtx = noAlpha.getContext('2d', {alpha:false});
  // now you can draw a partially transparent image and it will have a black background
  naCtx.drawImage(img, 20,20);
  
  
  //_____OPTION 2______//  
  
  // get the #whiteFill canvas' 2d context
  var wfCtx = whiteFill.getContext('2d');
  // set the context' fill color to white
  // this can be any CSS color argument
  wfCtx.fillStyle = 'white';
  // fill a rectangle as big as your canvas
  wfCtx.fillRect(0,0, whiteFill.width, whiteFill.height);
  // you've got a white background
  // now you can draw a partially transparent image and it will have a white background
  wfCtx.drawImage(img, 20,20);
  };
img.src = "https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png"
body{background-color: ivory;}
<canvas id="noAlpha" width="70" height="70"></canvas>
<canvas id="whiteFill" width="70" height="70"></canvas>

For the second question,

Is there a built in way to do rgba to 0/1 matrix transformation ?

No, or at least, not in my knowledge.

What I would do : keep the transparency of your images, except if you really need to consider white as transparent, in that case, see the last snippet of this answer.

Then it's quite easy to use the ctx.getImageData() method, loop through its data property and only check for the alpha values. (getImageData.data is a TypedArray representing the rgba values of the requested part of your canvas (red, green, blue and alpha channels)).

var img = new Image();

// to be able to use the getImageData method, we have to be sure we don't taint the canvas
// see http://stackoverflow.com/questions/34743987/canvas-image-crossplatform-insecure-error/34755162#34755162 for more info
img.crossOrigin = 'anonymous';

img.onload = function(){

  var ctx = source.getContext('2d');
  // draw your image and keep the transparency
  ctx.drawImage(img, 0,0);
  
  var yourArray = [];
  // get the canvas' imageData from position 0,0 and of the ful size of the canvas
  var imageData = ctx.getImageData(0,0, source.width, source.height);
  // get the rgba array of this imageData
  var data = imageData.data;
  // loop through all pixels, checking only for the alpha value
  for(var i=3; i<data.length-3; i+=4){
    // this is a non-transparent pixel
    if(data[i] > 0) {
      yourArray.push(1);
      // set the r,g,b values to 0 (black)
      data[i-3] = data[i-2] = data[i-1] = 0;
      // set the alpha value to 255
      data[i] = 255;
      }
    else{
      yourArray.push(0);
      }
    }
  console.log(yourArray);
  // draw this new image over the export canvas (could alos be the first one
  output.getContext('2d').putImageData(imageData, 0,0);
  };
img.src = "https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png"
body{background-color: ivory;}
<canvas id="source" width="32" height="32"></canvas>
<canvas id="output" width="32" height="32"></canvas>

Here is how to loop through all pixels values if you need to see white ones as transparent :

var img = new Image();
img.crossOrigin = 'anonymous';

img.onload = function(){

  var ctx = source.getContext('2d');

  ctx.drawImage(img, 0,0);
  
  var imageData = ctx.getImageData(0,0, source.width, source.height);
  var data = imageData.data;
  
  var yourArray = [];
  
  // loop through all pixels, checking for every rgba value
  for(var i=0; i<data.length; i+=4){
    // this is a non-white pixel
    if( data[i]+data[i+1]+data[i+2]+data[i+3] < 255*4 ) {
      yourArray.push(1);
      // set the r,g,b values to 0 (black)
      data[i] = data[i+1] = data[i+2] = 0;
      // set the alpha value to 255
      data[i+3] = 255;
     }else{
      yourArray.push(0);
      // set the alpha value to 0
      data[i+3] = 0;
     }
    }
  console.log(yourArray);
  output.getContext('2d').putImageData(imageData, 0,0);
  };
// an image with a white background
img.src = "https://dl.dropboxusercontent.com/s/iac97oiynwthrg5/someWhite.png"
body{background-color: ivory;}
<canvas id="source" width="284" height="383"></canvas>
<canvas id="output" width="284" height="383"></canvas>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • I used the last solution you proposed, to do the conversion on the client, instead of doing it on Server. Thanks dude :) – 7mza Feb 08 '16 at 16:37