web-dev-qa-db-ja.com

jcropを使用してクライアント側で画像をトリミングしてアップロードするにはどうすればよいですか?

私がやろうとしているのは、HTMLファイルアップロードコントロールを使用して画像を選択し、画像をレンダリングしてjcropを使用してトリミングすることです https://jsfiddle.net/govi20/spmc7ymp/

コードで使用されるID:

targetjcrop image target
photographfileupload control
previewキャンバスプレビュー
clear_selectionボタンは、選択した領域をクリアします

<script src="./js/jquery.min.js"></script>
<script src="./js/jquery.Jcrop.js"></script>
<script src="./js/jquery.color.js"></script>

jcropのセットアップ。

<script type="text/javascript">
jQuery(function($){

var api;

$('#target').Jcrop({
  // start off with jcrop-light class
  bgOpacity: 0.5,
  keySupport: false,
  bgColor: 'black',
  minSize:[240,320],
  maxSize:[480,640],
  onChange : updatePreview,
  onSelect : updatePreview, 
  height:160,
  width:120,
  addClass: 'jcrop-normal'
},function(){
  api = this;
  api.setSelect([0,0,240,320]);
  api.setOptions({ bgFade: true });
  api.ui.selection.addClass('jcrop-selection');
  });

});

選択をクリアします。

jQuery('#clear_selection').click(function(){
  $('#target').Jcrop({    

      setSelect: [0,0,0,0],
    });
});

アップロードされた画像をjcropパネルに表示するには

function readURL(input) {

    if (input.files && input.files[0]) {
        var reader = new FileReader();
        reader.onload = function (e) {
            $('#target').attr('src', e.target.result);
            setProperties();       
        }
        reader.readAsDataURL(input.files[0]);
    }
}

function setProperties(){
   $('#target').Jcrop({         
              setSelect: [0,0,240,320]
        }); 
}
$("#photograph").change(function(){
    readURL(this);     
});

次のコードを使用して、キャンバス上の画像をトリミングしてレンダリングしています。

    function make_base() {
        console.log("make_base called");
        var base_image = new Image();
        base_image.src = '';
        base_image.onload = function () {
            context.drawImage(base_image, 0, 0);
        }
    }

    var canvas = document.getElementById('preview'),
    context = canvas.getContext('2d');

    make_base();
    function updatePreview(c) {
        console.log("called");
        if(parseInt(c.w) > 0) {
            // Show image preview
            var imageObj = $("#target")[0];
            var canvas = $("#preview")[0];
            var context = canvas.getContext("2d");
            context.drawImage(imageObj, c.x, c.y, c.w, c.h, 0, 0, canvas.width, canvas.height);
        }
    };

私がここで直面している問題のリストは次のとおりです。

  1. updatePreview関数は選択時に呼び出されないため、キャンバスは表示されません。
  2. トリミング選択ボックスはドラッグできません(bootstrap CSSを使用しています。依存関係の問題が疑われます)。
  3. CanvasはHTML5要素、つまりエンドユーザーがHTML5互換性のあるブラウザ、私は何百万人ものユーザーがいるアプリケーションに取り組んでいます。ユーザーに最新のブラウザーの使用を強制することは解決策ではありません。では、フォールバックメカニズムはどうあるべきでしょうか?
12
Govinda Sakhare

Seahorsepipの答えは素晴らしいです。非フォールバック回答について多くの改善を行いました。

http://jsfiddle.net/w1Lh4w2t/

Imageオブジェクトが同様に機能する場合(フォールバックをサポートしていない限り)、奇妙な隠されたpng処理を行わないことをお勧めします。

var jcrop_api;
var canvas;
var context;
var image;
var prefsize;

それでも私たちはそうですが、最後にそのデータをキャンバスから取り出して、最後にだけそのフィールドに入れる方が良いでしょう。

function loadImage(input) {
  if (input.files && input.files[0]) {
    var reader = new FileReader();
    reader.onload = function(e) {
      image = new Image();
      image.src = e.target.result;
      validateImage();
    }
    reader.readAsDataURL(input.files[0]);
  }
}

しかし、単に切り抜くよりも多くの機能が必要な場合、jcropを挿入されたキャンバスにアタッチすると(更新時にjcropで破棄されます)。キャンバスでできることは何でも簡単に実行でき、その後再度validateImage()を実行して、更新された画像を所定の場所に表示できます。

function validateImage() {
  if (canvas != null) {
    image = new Image();
    image.src = canvas.toDataURL('image/png');
  }
  if (jcrop_api != null) {
    jcrop_api.destroy();
  }
  $("#views").empty();
  $("#views").append("<canvas id=\"canvas\">");
  canvas = $("#canvas")[0];
  context = canvas.getContext("2d");
  canvas.width = image.width;
  canvas.height = image.height;
  context.drawImage(image, 0, 0);
  $("#canvas").Jcrop({
    onSelect: selectcanvas,
    onRelease: clearcanvas,
    boxWidth: crop_max_width,
    boxHeight: crop_max_height
  }, function() {
    jcrop_api = this;
  });
  clearcanvas();
}

次に、送信時に、applyCrop()やapplyScale()などの保留中の操作を送信し、必要なものがある場合はフォールバック用の非表示フィールドにデータを追加します。その後、キャンバスを変更するだけで簡単にシステムを作成でき、キャンバスを送信すると、データが適切に送信されます。

function applyCrop() {
  canvas.width = prefsize.w;
  canvas.height = prefsize.h;
  context.drawImage(image, prefsize.x, prefsize.y, prefsize.w, prefsize.h, 0, 0, canvas.width, canvas.height);
  validateImage();
}

キャンバスがdivビューに追加されます。

 <div id="views"></div>

添付ファイルをPHP(drupal)でキャッチするには、次のようなものを使用しました。

    function makeFileManaged() {
        if (!isset($_FILES['croppedfile']))
            return NULL;
        $path = $_FILES['croppedfile']['tmp_name'];
        if (!file_exists($path))
            return NULL;
        $result_filename = $_FILES['croppedfile']['name'];
        $uri = file_unmanaged_move($path, 'private://' . $result_filename, FILE_EXISTS_RENAME);
        if ($uri == FALSE)
            return NULL;
        $file = File::Create([
                    'uri' => $uri,
        ]);
        $file->save();
        return $file->id();
    }
18
Tatarize

基本的なhtml 5コードは次のとおりです。

https://jsfiddle.net/zm7e0jev/

このコードは画像をトリミングし、プレビューを表示し、入力要素の値をbase64エンコードされたトリミングされた画像に設定します。

次の方法で、PHPで画像ファイルを取得できます。

//File destination
$destination = "/folder/cropped_image.png";
//Get convertable base64 image string
$image_base64 = $_POST["png"];
$image_base64 = str_replace("data:image/png;base64,", "", $image_base64);
$image_base64 = str_replace(" ", "+", $image_base64);
//Convert base64 string to image data
$image = base64_decode($image_base64);
//Save image to final destination
file_put_contents($destination, $image);

Base64画像文字列を投稿変数として送信すると、サーバー投稿サイズ制限があり、base64エンコードにより、トリミングされた画像ファイルのサイズがさらに大きくなり(〜33%)、トリミングされた画像の生データが大きくなり、アップロードにさらに時間がかかります。

投稿サイズの制限を設定するには: 投稿リクエストのサイズ制限は?

たとえば、DoS攻撃では、増加した投稿サイズ制限が悪用される可能性があることに注意してください。

代わりに、base64でトリミングされた画像をデータblobに変換してから、ファイルとして送信するときにフォームに追加することをお勧めします。

https://jsfiddle.net/g3ysk6sf/

その後、次の方法でphpで画像ファイルを取得できます。

//File destination
$destination = "/folder/cropped_image.png";
//Get uploaded image file it's temporary name
$image_tmp_name = $_FILES["cropped_image"]["tmp_name"][0];
//Move temporary file to final destination
move_uploaded_file($image_tmp_name, $destination);

更新:

FormData()はIE10で部分的にのみサポートされており、IEの古いバージョンではサポートされていません

したがって、base64文字列をフォールバックとして送信することをお勧めしますが、これにより大きな画像で問題が発生するため、ファイルサイズを確認し、画像が特定のサイズを超えるとエラーポップアップを表示する必要があります。

動作するようになったら、以下のフォールバックコードを使用して更新を投稿します。

アップデート2:

IE10以下のフォールバックを追加しました。

https://jsfiddle.net/oupxo3pu/

唯一の制限は、IE10以下を使用する場合に送信できる画像サイズです。画像サイズが大きすぎる場合、jsコードはエラーをスローします。ポスト値で機能する最大サイズはサーバーごとに異なります。jsコードには最大サイズを設定する変数があります。

以下のphpコードは、上記のフォールバックで動作するように適合されています。

//File destination
$destination = "/folder/cropped_image.png";
if($_POST["png"]) {//IE10 and below
    //Get convertable base64 image string
    $image_base64 = $_POST["png"];
    $image_base64 = str_replace("data:image/png;base64,", "", $image_base64);
    $image_base64 = str_replace(" ", "+", $image_base64);
    //Convert base64 string to image data
    $image = base64_decode($image_base64);
    //Save image to final destination
    file_put_contents($destination, $image);
} else if($_FILES["cropped_image"]) {//IE11+ and modern browsers
    //Get uploaded image file it's temporary name
    $image_tmp_name = $_FILES["cropped_image"]["tmp_name"][0];
    //Move temporary file to final destination
    move_uploaded_file($image_tmp_name, $destination);
}
Canvas要素のフォールバックコードはまだありません。調査中です。
古いブラウザのフォールバックでの投稿サイズの制限は、私が古いブラウザのサポートを自分でやめた理由の1つです。

アップデート3:

IE8のcanvas要素に推奨するフォールバック:

http://flashcanvas.net/

トリミングコードが必要とするすべてのキャンバス機能をサポートします。

フラッシュが必要なことに注意してください。フラッシュを必要としないキャンバスフォールバック(explorercanvas)がありますが、トリミングされた画像を保存する必要がある関数toDataURL()をサポートしていません。

18
seahorsepip