PHP read_exif_data y ajustar orientación

79

Estoy usando el siguiente código para rotar una imagen jpeg cargada si la orientación está apagada. Solo tengo problemas con las imágenes cargadas desde iPhones y Android.

if(move_uploaded_file($_FILES['photo']['tmp_name'], $upload_path . $newfilename)){
            chmod($upload_path . $newfilename, 0755);
            $exif = exif_read_data($upload_path . $newfilename);
            $ort = $exif['IFD0']['Orientation'];
            switch($ort)
            {

                case 3: // 180 rotate left
                    $image->imagerotate($upload_path . $newfilename, 180, -1);
                    break;


                case 6: // 90 rotate right
                    $image->imagerotate($upload_path . $newfilename, -90, -1);
                    break;

                case 8:    // 90 rotate left
                    $image->imagerotate($upload_path . $newfilename, 90, -1);
                    break;
            }
            imagejpeg($image, $upload_path . $newfilename, 100);
            $success_message = 'Photo Successfully Uploaded';
        }else{
            $error_count++;
            $error_message = 'Error: Upload Unsuccessful<br />Please Try Again';
        }

¿Estoy haciendo algo mal con la forma en que leo los datos EXIF ​​del jpeg? No está rotando las imágenes como se supone.

Esto es lo que sucede cuando ejecuto un var_dump ($ exif);

array(41) {
    ["FileName"]=> string(36) "126e7c0efcac2b76b3320e6187d03cfd.JPG"
    ["FileDateTime"]=> int(1316545667)
    ["FileSize"]=> int(1312472)
    ["FileType"]=> int(2)
    ["MimeType"]=> string(10) "image/jpeg"
    ["SectionsFound"]=> string(30) "ANY_TAG, IFD0, THUMBNAIL, EXIF"
    ["COMPUTED"]=> array(8) {
        ["html"]=> string(26) "width="2048" height="1536""
        ["Height"]=> int(1536)
        ["Width"]=> int(2048)
        ["IsColor"]=> int(1)
        ["ByteOrderMotorola"]=> int(1)
        ["ApertureFNumber"]=> string(5) "f/2.8"
        ["Thumbnail.FileType"]=> int(2)
        ["Thumbnail.MimeType"]=> string(10) "image/jpeg" }
        ["Make"]=> string(5) "Apple"
        ["Model"]=> string(10) "iPhone 3GS"
        ["Orientation"]=> int(6)
        ["XResolution"]=> string(4) "72/1"
            ["YResolution"]=> string(4) "72/1" ["ResolutionUnit"]=> int(2) ["Software"]=> string(5) "4.3.5" ["DateTime"]=> string(19) "2011:09:16 21:18:46" ["YCbCrPositioning"]=> int(1) ["Exif_IFD_Pointer"]=> int(194) ["THUMBNAIL"]=> array(6) { ["Compression"]=> int(6) ["XResolution"]=> string(4) "72/1" ["YResolution"]=> string(4) "72/1" ["ResolutionUnit"]=> int(2) ["JPEGInterchangeFormat"]=> int(658) ["JPEGInterchangeFormatLength"]=> int(8231) } ["ExposureTime"]=> string(4) "1/15" ["FNumber"]=> string(4) "14/5" ["ExposureProgram"]=> int(2) ["ISOSpeedRatings"]=> int(200) ["ExifVersion"]=> string(4) "0221" ["DateTimeOriginal"]=> string(19) "2011:09:16 21:18:46" ["DateTimeDigitized"]=> string(19) "2011:09:16 21:18:46" ["ComponentsConfiguration"]=> string(4) "" ["ShutterSpeedValue"]=> string(8) "3711/949" ["ApertureValue"]=> string(9) "4281/1441" ["MeteringMode"]=> int(1) ["Flash"]=> int(32) ["FocalLength"]=> string(5) "77/20" ["SubjectLocation"]=> array(4) { [0]=> int(1023) [1]=> int(767) [2]=> int(614) [3]=> int(614) } ["FlashPixVersion"]=> string(4) "0100" ["ColorSpace"]=> int(1) ["ExifImageWidth"]=> int(2048) ["ExifImageLength"]=> int(1536) ["SensingMethod"]=> int(2) ["ExposureMode"]=> int(0) ["WhiteBalance"]=> int(0) ["SceneCaptureType"]=> int(0) ["Sharpness"]=> int(1) }
Jeff Thomas
fuente
Tenga en cuenta que este código volverá a comprimir la imagen de origen, incluso si no se necesita rotación.
Marc B
Mi problema en este momento es que las imágenes que deben rotarse no se rotan.
Jeff Thomas
Haz una var_dump($exif)para ver qué están produciendo los teléfonos Android en cuanto a datos de rotación.
Marc B
1
Ok, limpié el basurero allí. Obviamente. el campo de orientación no está en una sección 'IFD0', es $exif['COMPUTED']['Orientation']y tiene valor 6.
Marc B
1
$ exif ['Orientación']; funciona bien para mí. Podría ser una mejor opción en comparación con $ exif ['alguna_sección'] ['Orientación'];
demosten el

Respuestas:

63

La documentación de imagerotate se refiere a un tipo diferente para el primer parámetro del que usa:

Un recurso de imagen, devuelto por una de las funciones de creación de imágenes, como imagecreatetruecolor ().

A continuación, se muestra un pequeño ejemplo del uso de esta función:

function resample($jpgFile, $thumbFile, $width, $orientation) {
    // Get new dimensions
    list($width_orig, $height_orig) = getimagesize($jpgFile);
    $height = (int) (($width / $width_orig) * $height_orig);
    // Resample
    $image_p = imagecreatetruecolor($width, $height);
    $image   = imagecreatefromjpeg($jpgFile);
    imagecopyresampled($image_p, $image, 0, 0, 0, 0, $width, $height, $width_orig, $height_orig);
    // Fix Orientation
    switch($orientation) {
        case 3:
            $image_p = imagerotate($image_p, 180, 0);
            break;
        case 6:
            $image_p = imagerotate($image_p, -90, 0);
            break;
        case 8:
            $image_p = imagerotate($image_p, 90, 0);
            break;
    }
    // Output
    imagejpeg($image_p, $thumbFile, 90);
}
Daniel Bleisteiner
fuente
Por alguna razón, las imágenes creadas por Android 4.1.2 no necesitan rotarse, solo cargue la imagen con "imagecreatefromjpen ()" y luego guárdela de nuevo con "imagejpeg ()". ¿Sabes por qué?
doron
76

Basado en el código de Daniel, escribí una función que simplemente rota una imagen si es necesario, sin remuestrear.

GD

function image_fix_orientation(&$image, $filename) {
    $exif = exif_read_data($filename);

    if (!empty($exif['Orientation'])) {
        switch ($exif['Orientation']) {
            case 3:
                $image = imagerotate($image, 180, 0);
                break;

            case 6:
                $image = imagerotate($image, -90, 0);
                break;

            case 8:
                $image = imagerotate($image, 90, 0);
                break;
        }
    }
}

Versión de una línea (GD)

function image_fix_orientation(&$image, $filename) {
    $image = imagerotate($image, array_values([0, 0, 0, 180, 0, 0, -90, 0, 90])[@exif_read_data($filename)['Orientation'] ?: 0], 0);
}

ImageMagick

function image_fix_orientation($image) {
    if (method_exists($image, 'getImageProperty')) {
        $orientation = $image->getImageProperty('exif:Orientation');
    } else {
        $filename = $image->getImageFilename();

        if (empty($filename)) {
            $filename = 'data://image/jpeg;base64,' . base64_encode($image->getImageBlob());
        }

        $exif = exif_read_data($filename);
        $orientation = isset($exif['Orientation']) ? $exif['Orientation'] : null;
    }

    if (!empty($orientation)) {
        switch ($orientation) {
            case 3:
                $image->rotateImage('#000000', 180);
                break;

            case 6:
                $image->rotateImage('#000000', 90);
                break;

            case 8:
                $image->rotateImage('#000000', -90);
                break;
        }
    }
}
Jonathan
fuente
Para Imagick, uso getImageOrientation () para recuperar la orientación y luego, después de rotar la imagen, establezco el valor de Orientación Exif correcto a través de $ image-> setImageOrientation (\ Imagick :: ORIENTATION_TOPLEFT);
Tilman
¿Tiene una solución para WideImage?
Yami Medina
En algunos casos, la función imagick getImageOrientation()no me funcionó correctamente incluso con imágenes sin procesar convertidas. El código anterior funcionó perfectamente.
rokdd
En la primera versión (GD), ¿qué debo pasar por & $ image donde llamo a esta función?
Bharat Maheshwari
2
para quien no entienda cómo pasar el parámetro & $ image del archivo local, use así: $ im = @imagecreatefromjpeg ($ local_filename); image_fix_orientation ($ im, $ nombre_archivo_local); if ($ im) {imagejpeg ($ im, $ nombre_archivo_local); imagedestroy ($ im); }
woheras
43

Función más simple para aquellos que cargan una imagen, simplemente gira automáticamente si es necesario.

function image_fix_orientation($filename) {
    $exif = exif_read_data($filename);
    if (!empty($exif['Orientation'])) {
        $image = imagecreatefromjpeg($filename);
        switch ($exif['Orientation']) {
            case 3:
                $image = imagerotate($image, 180, 0);
                break;

            case 6:
                $image = imagerotate($image, -90, 0);
                break;

            case 8:
                $image = imagerotate($image, 90, 0);
                break;
        }

        imagejpeg($image, $filename, 90);
    }
}
usuario462990
fuente
1
Convirtió esta respuesta en un paquete de redacción simple, que se puede encontrar en github (clase con un solo método): github.com/diversen/image-auto-rotate
dennis
Utiliza valores de grado incorrectos. En el caso 6 necesitas 90 y en el caso 8 necesitas -90 grados.
bernhardh
función muy útil, si alguien ve esta advertencia Tamaño de IFD ilegal , puede usar el operador @, por ejemplo : $exif = @exif_read_data($filename);
chebaby
@ user462990 Esta función funciona bien pero solo para imágenes servidas localmente. ¿Cómo se puede pasar una URL de imagen? Tengo una imagen en s3 que necesitaba para manipular la orientación.
ultrasamad
12

¿Por qué nadie está considerando los casos reflejados 2,4,5,7? Hay 4 casos más en terrenos de orientación exif:

ingrese la descripción de la imagen aquí

Aquí hay una solución completa que toma un nombre de archivo:

function __image_orientate($source, $quality = 90, $destination = null)
{
    if ($destination === null) {
        $destination = $source;
    }
    $info = getimagesize($source);
    if ($info['mime'] === 'image/jpeg') {
        $exif = exif_read_data($source);
        if (!empty($exif['Orientation']) && in_array($exif['Orientation'], [2, 3, 4, 5, 6, 7, 8])) {
            $image = imagecreatefromjpeg($source);
            if (in_array($exif['Orientation'], [3, 4])) {
                $image = imagerotate($image, 180, 0);
            }
            if (in_array($exif['Orientation'], [5, 6])) {
                $image = imagerotate($image, -90, 0);
            }
            if (in_array($exif['Orientation'], [7, 8])) {
                $image = imagerotate($image, 90, 0);
            }
            if (in_array($exif['Orientation'], [2, 5, 7, 4])) {
                imageflip($image, IMG_FLIP_HORIZONTAL);
            }
            imagejpeg($image, $destination, $quality);
        }
    }
    return true;
}
David Vielhuber
fuente
Excelente solucion. Este fue un buen punto porque muchos usuarios cargan imágenes reflejadas y tienen problemas con su imagen final.
Albert Thompson
6

Por si acaso alguien se encuentra con esto. Por lo que puedo distinguir, algunas de las declaraciones de cambio anteriores son incorrectas.

Según la información aquí , debería ser:

switch ($exif['Orientation']) {
    case 3:
        $image = imagerotate($image, -180, 0);
        break;
    case 6:
        $image = imagerotate($image, 90, 0);
        break;
    case 8:
        $image = imagerotate($image, -90, 0);
        break;
} 
mr_crazy_pants
fuente
6

Probablemente valga la pena mencionar que si está usando ImageMagick desde la línea de comando, puede usar la opción -auto-orient que rotará automáticamente la imagen en función de los datos de orientación EXIF ​​existentes.

convert -auto-orient /tmp/uploadedImage.jpg /save/to/path/image.jpg

Tenga en cuenta: si los datos EXIF ​​se eliminaron antes del proceso, no funcionarán como se describe.

Gato
fuente
2

Aquí estoy explicando todo, uso Laravel y uso el Paquete de intervención de imágenes.

En primer lugar, obtengo mi imagen y la envío a mi otra función para cambiar el tamaño y alguna otra funcionalidad, si no la necesitamos, puede omitir ...

Coge el archivo con un método en mi controlador,

 public  function getImageFile(Request $request){
    $image = $request->image;
    $this->imageUpload($image);
}

Ahora, lo envío para cambiar el tamaño y obtener el nombre y la extensión de la imagen ...

public function  imageUpload($file){
    ini_set('memory_limit', '-1');
    $directory = 'uploads/';
    $name = str_replace([" ", "."], "_", $file->getClientOriginalName()) . "_";
    $file_name = $name . time() . rand(1111, 9999) . '.' . $file->getClientOriginalExtension();
    //path set
    $img_url = $directory.$file_name;
    list($width, $height) = getimagesize($file);
    $h = ($height/$width)*600;
    Image::make($file)->resize(600, $h)->save(public_path($img_url));
    $this->image_fix_orientation($file,$img_url);
    return $img_url;
}

Ahora llamo a mi función de orientación de imagen,

 public function image_fix_orientation($file,$img_url ) {
    $data = Image::make($file)->exif();
    if (!empty($data['Orientation'])) {
        $image = imagecreatefromjpeg($file);
        switch ($data['Orientation']) {
            case 3:
                $image = imagerotate($image, 180, 0);
                break;

            case 6:
                $image = imagerotate($image, -90, 0);
                break;

            case 8:
                $image = imagerotate($image, 90, 0);
                break;
        }

        imagejpeg($image, $img_url, 90);
    }

}

Y eso es todo...

MARYLAND. ABU TALHA
fuente
1

Odio intervenir con otro conjunto de valores de orientación, pero en mi experiencia al usar cualquiera de los valores enumerados anteriormente, siempre terminaba con imágenes al revés cuando cargaba tomas de orientación vertical directamente desde un iPhone. Aquí está la declaración de cambio con la que terminé.

switch ($exif['Orientation']) {
        case 3:
            $image = imagerotate($image, -180, 0);
            break;

        case 6:
            $image = imagerotate($image, -90, 0);
            break;

        case 8:
            $image = imagerotate($image, 90, 0);
            break;
    }
Brad Root
fuente
1

jhead -autorot jpegfile.jpg

También es una forma útil de abordar esto.

jhead es un programa estándar en Linux (use 'sudo apt-get install jhead' para instalar), esta opción mira la orientación y rota la imagen correctamente y sin pérdidas solo si es necesario. Luego también actualiza correctamente los datos EXIF.

De esta manera, puede procesar un jpeg (o varios jpegs en una carpeta) de una manera simple de una sola pasada que corrige los problemas de rotación de forma permanente.

Por ejemplo: jhead -autorot * .jpg arreglará una carpeta completa de imágenes jpeg de la manera que el OP requiere en la pregunta inicial.

Si bien no es técnicamente PHP, leí este hilo y luego usé mi sugerencia jhead en su lugar, llamé desde una llamada al sistema PHP () para lograr los resultados que buscaba, que coincidían con los OP: rotar imágenes para que cualquier software (como 'fbi 'en Raspbian) podría mostrarlos correctamente.

En vista de esto, pensé que otros podrían beneficiarse al saber con qué facilidad jhead resuelve este problema y publiqué la información aquí solo con fines informativos, porque nadie lo había mencionado anteriormente.

GPW
fuente
Tu respuesta se marcó como de baja calidad porque era corta. Intente explicar su solución con más profundidad.
Derek Brown
1

También utilicé la orientate()intervención de formularios y funciona perfectamente.

    $image_resize = Image::make($request->file('photo'));
    $image_resize->resize(1600, null,function ($constraint)
    {
        $constraint->aspectRatio();
    });
    $filename = $this->checkFilename();

    $image_resize->orientate()->save($this->photo_path.$filename,80);
c0ld
fuente
1

Aquí está mi función PHP 7 inspirada en @ user462990:

/**
 * @param string $filePath
 *
 * @return resource|null
 */
function rotateImageByExifOrientation(string $filePath)
{
    $result = null;

    $exif = exif_read_data($filePath);
    if (!empty($exif['Orientation'])) {
        $image = imagecreatefromjpeg($filePath);
        if (is_resource($image)) {
            switch ($exif['Orientation']) {
                case 3:
                    $result = imagerotate($image, 180, 0);
                    break;

                case 6:
                    $result = imagerotate($image, -90, 0);
                    break;

                case 8:
                    $result = imagerotate($image, 90, 0);
                    break;
            }
        }
    }

    return $result;
}

uso:

    $rotatedFile = rotateImageByExifOrientation($absoluteFilePath);
    if (is_resource($rotatedFile)) {
        imagejpeg($rotatedFile, $absoluteFilePath, 100);
    }
Sebastián Viereck
fuente
-1

La imagen de intervención tiene un método orientate().

$img = Image::make('foo.jpg')->orientate();
Damien Bezborodow
fuente