简体   繁体   中英

Rgba to basic matrix (0 for white pixel, 1 for anything else) transformation with BufferedImage

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.

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> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM