[Résolu]Réduire la taille d'un fichier .jpeg de 5Mo à  500ko

mybofymybofy Membre
février 2014 modifié dans API AppKit #1

Bonjour


 


Comment réduire la taille d'un fichier .jpeg de 5Mo à  500ko ?


 


L'essai de ImageIO est infructueux.


ça marche avec la fonction thumbnail, mais la réduction est trop importante : impossible d'obtenir un fichier réduit de plus de 160x160 px, quelle que soit la taille du fichier d'origine !


 


Je précise qu'il s'agit de fichiers, et pas d'affichage.


 


Merci des pistes.


Réponses

  • AliGatorAliGator Membre, Modérateur
    Tu veux réduire quoi / jouer sur quel paramètre ?

    Qualité ? Dimensions ? Résolution ? Compression ? Format ?

    Quand je lis "réduire la taille d'un fichier JPEG" comme tu le mets dans ta question, sans autre précision moi je lis "réduire la taille du fichier, pas réduire la taille de l'image" et donc je lis "réduire la qualité / augmenter la compression" (et du coup je vois pas ce que ImageIO et sa fonction thumbnail viendraient faire ici si c'est sur cet axe)

    Bref, plus de précisions sur le besoin ?
  • CéroceCéroce Membre, Modérateur
    février 2014 modifié #3
    On ne peut pas connaà®tre la taille d'un fichier JPEG avant de l'avoir enregistré.

    Il y a deux manière de réduire la taille du fichier:
    1) réduire la résolution
    2) augmenter le taux de compression

    L'approche 2) n'est pas très probante parce que la perte de qualité est très visible.

    Pour l'approche 1), on veut que l'image soit 10 x plus légère, donc on veut 10x moins de pixels, donc il faut diviser les dimensions par racine carrée de 10, soit 3,16.
    - ouvrir l'image.
    - la dessiner dans un contexte offscreen
    - enregistrer le contexte dans un fichier JPEG.

    Voici pour les étapes. Concrètement, je ne sais pas trop ce que NSImage / NSBitmapImageRep savent faire ou pas, j'ai abandonné ces classes qui posent souvent beaucoup de problèmes. On peut peut-être.

    Avec Core Graphics:
    - Ouvrir l'image avec CGImageSource.
    - Créer un CGBitmapContext 10 x plus petit que l'image
    - Dessiner l'image dedans.
    - Enregistrer le bitmap context avec CGImageDestination.

    Note que CGImageSource ne gère pas automatiquement les rotations EXIF.

    Avant tout chose, cherche parmi le code open-source, sans doute quelqu'un l'a-t-il déjà  implémenté.
  • Bonjour,


     


    Il faut savoir que le format .jpeg est un format de compression destructif. Certaine informations contenuent dans l'image vont être perdues. Plus ou moins selon le degré de compression. Si tu veux uniquement parler de la taille du fichier image, je ne vois pas ce que vient faire la taille de l'affichage de l'image dans ta demande.


  • tabliertablier Membre
    février 2014 modifié #5

    On ne peut pas connaà®tre la taille d'un fichier JPEG avant de l'avoir enregistré



    Si, avec GC lorsque je réduis la qualité ou les dimensions il me donne la taille du fichier résultant avant que je l'enregistre. Je pense que cela est fait par calcul car la taille du résultat varie simultanément au déplacement du réglage  "Qualité".


  • CéroceCéroce Membre, Modérateur
    Alors je rectifie: on ne peut pas connaà®tre la taille des données JPEG avant d'avoir compressé l'image. Par le principe même de la compression de données, la taille finale dépend de la redondance des informations.
  • AliGatorAliGator Membre, Modérateur
    Non ce que fait GC (comme les autres apps qui font ça) c'est une estimation très grossière, genre à  l'aide d'abaques moyennes.

    Mais par exemple une image qui n'est rien d'autre que des pixels de la même couleur (couleur unie sur toute l'image quoi) on pourra avoir une super compression (un tout petit poids de fichier pour aucune dégradation de qualité). Une image qui contient beaucoup de contours, ou de changements de couleurs et beaucoup de passages d'une couleur à  une autre), tu vas perdre beaucoup plus vite en qualité quand tu augmentes la compression.

    En gros, pour parler technique, le JPEG utilise entre autres :
    (1) découpage en macroblocks
    (2) compression par Transformée de Fourier
    (3) un élagage des coefficients faibles
    (4) une quantification.

    Donc à  cause du (2) plus tu as une image qui a une haute fréquence (= beaucoup de changements de couleur nets, peu de macroblocs qui soient unis mais au contraire beaucoup qui sont composés de détails) plus tu vas avoir des forts coefficients dans ta FFT et donc pus il va falloir garder des coefficients pour être au plus proche du signal d'origine (de ton image d'origine) / pour limiter la perte de qualité.

    Si tu dois garder beaucoup de coefficients, soit le (3) va de toute façon être obligé d'en enlever même s'ils sont pas si faibles donc tu va perdre une fréquence qui n'est pas si anodine donc perdre en qualité / perte de détails / ajout de flou.


    Conclusion : une image unie ou ayant une faible fréquence (faibles transitions de couleur d'un pixel au suivant) va permettre un fort taux de compression sans grosse perte de qualité. Une image d'un dessin au trait, avec des contours fins très détaillés, etc va très très vite perdre en qualité / précision même avec une compression faible. Le résultat dépend donc énormément de ton image.
  • tabliertablier Membre
    février 2014 modifié #8

    Ok, je n'ai rien dit. On ne peut donc pas connaitre la taille du résultat tant qu'on n'a pas fait la transformation.


    Je n'avais jamais pensé appliquer la FFT à  des blocs d'images. Pour moi une FFT est une conversion Amplitude-temps vers Fréquence-temps. Je ne vois pas ou est l'aspect temps dans une image fixe. Sauf durant le temps du calcul, ou le temps de prise de l'image bien sur . Mais peut être remplace-t-on le temps par l'espace. Bon, il y a presque  30 ans que je ne m'occupe plus de ce genre de chose.


  • AliGatorAliGator Membre, Modérateur
    février 2014 modifié #9
    Une FFT c'est certes pour un traitement du signal f(t) une transformation de l'espace des fréquences et donc surtout une décomposition de ton signal d'origine en somme de fréquences de plus ou moins grandes amplitudes ( f(t) = a0.cos(wt) + a1.cos(2wt) + ... ). C'est vrai qu'en général la variable c'est le temps mais ce n'est pas une obligation.


    ça reste une formule / transformée utile dans le traitement du signal, que ce signal soit audio, temporel ou spatial le principe reste le même.

    Une FFT dans le traitement du signal numérique représentant une image c'est une FFT 2D puisqu'une image c'est une fonction f(x,y) de |R x |R vers l'espace des couleurs mais le principe est le même.


    Si on prend un exemple simple, soit une image en niveaux de gris, la FFT 2D consiste tout simplement à  décomposer l'image en la somme d'une image unie (valeur moyenne de ton signal / couleur moyenne de ton image / composante continue) + une image coupée en 2 moitiés verticales (l'une blanche l'autre noire) + une image coupée en 2 moitiés horizontales (idem) (composante de la fréquence principale w0) + une image découpée en 4 verticalement + une coupée en 4 horizontalement (composante de la fréquence 2*w0) etc etc (bon c'est réducteur comme explication mais tu vois l'idée)


    Bref c'est le même principe que quand tu essayes de faire une décomposition en composantes principales / série de Fourier, tu décomposes ton signal en une combinaison linéaire de fonctions sinusoà¯dales à  différentes fréquences, que ce soit des fréquences au sens temporelles (1Hz, 2Hz, 4Hz, 8Hz, ...) ou au sens spatial sur l'axe des x et/ou des y (non pas 1Hz = 1 période de 1s mais période de largeur totale de l'image ; 2Hz = 2 périodes non pas par seconde mais par largeur de l'image, etc). Ici w c'est pas une fréquence en Hertz donc le nombre de périodes de ta sinusoà¯de en une seconde mais le nombre de périodes de ta sinusoà¯de en une largeur d'image. Et t c'est pas le temps mais c'est l'axe des x de ton image. Et f est plus une fonction temps->amplitude mais une fonction point (x,y)->couleur mais c'est les mêmes concepts pour autant.


    On reste dans une transformation qui fait passer du domaine spatial (image x,y) au domaine fréquentiel (sinusoà¯des allant des basses aux hautes fréquences donc des images unies, composante continue aux images hautes fréquences donc qui changent de couleur tous les pixels avec des écarts importants (gradient fort / dérivée à  pics élevés))


    PS: Pour info le format JPEG2000 utilise le même genre de technique que le JPEG mais utilise une décomposition en "ondelettes" et non plus en signaux sinusoà¯daux
  • tabliertablier Membre
    février 2014 modifié #11

    Ok, je vois mieux comment ont s'y prend.  


    Du coup je suis retourné dans ma bibliothèque, puis j'ai vérifié sur le web: le bouquin de Max 5eme édition est toujours en vente! Surprenant!  à  ma connaissance, Max est décédé il y a quelques années et la deuxième édition du livre date de 1977. Jacques Max était un chef de laboratoire (puis de division) du LETI et les gens dont les noms sont sur la couverture étaient des collègues. J'aimais bien Max qui, quand on se croisait, m'appelait mon neveu et que j'appelais mon oncle!


    Souvenir souvenir...  (chanson connu)




  • Non ce que fait GC (comme les autres apps qui font ça) c'est une estimation très grossière, genre à  l'aide d'abaques moyennes.




     


     


    Photoshop se débrouille autrement, je pense qu'il fait une véritable compression en parallèle de l'affichage de la boà®te de dialogue (Enregistrer pour le web...)



  • Photoshop se débrouille autrement, je pense qu'il fait une véritable compression en parallèle de l'affichage de la boà®te de dialogue (Enregistrer pour le web...)




    C'est ce que je pense aussi. Il doit sauvegarder quelque part en temp pour avoir un truc qui au final est relativement précis.

  • Pour #2 AliGator


    Effectivement, je suis intervenu dans une autre discussion où l'on évoquait ImageIO. Après essais, cela ne correspond pas à  ce que je souhaite.


     


    Pour #3 Céroce


    C'est sans doute la bonne solution. Je vais m'y lancer, mais il va me falloir des jours. Il y a beaucoup de paramètres auxquels je ne comprends pas grand chose.


    Où faut-il chercher "le code open-source" ?


    Tous mes essais sur Google me proposent des applications, ce que je veux éviter précisément, ou des réductions à  l'écran, ce que je sais à  peu près faire.


     


    Merci à  tous et à  bientôt. 


  • Deuxième étape :




    Avec Core Graphics:

    - Ouvrir l'image avec CGImageSource.

    - Créer un CGBitmapContext 10 x plus petit que l'image




     


    Fichier DSC00015.JPG, 7.4Mo


    Déjà  un problème : 



    _photoData = [NSData dataWithContentsOfFile:_photoPath];
    CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)_photoData, NULL);
    CGImageRef image = CGImageSourceCreateImageAtIndex (imageSource, 0, NULL);
    NSLog(@CGImageGetWidth(image) : %lu, CGImageGetWidth(image));
    NSLog(@CGImageGetHeight(image) : %lu, CGImageGetHeight(image));
    NSLog(@CGImageGetBitsPerComponent(image) : %lu, CGImageGetBitsPerComponent(image));
    NSLog(@CGImageGetBytesPerRow(image) : %lu, CGImageGetBytesPerRow(image));
    NSLog(@CGImageGetColorSpace(image) : %@", CGImageGetColorSpace(image));
    NSLog(@kCGBitmapByteOrderDefault : %u, kCGBitmapByteOrderDefault);
    CGContextRef context = CGBitmapContextCreate( NULL, CGImageGetWidth(image), CGImageGetHeight(image), CGImageGetBitsPerComponent(image), CGImageGetBytesPerRow(image), CGImageGetColorSpace(image), kCGBitmapByteOrderDefault );

    J'imaginais qu'avec les paramètres d'une image qui marche, je pourrais créer un context correct. Eh bien ! non.


    Dans la console :



    2014-02-27 20:23:06.626 Wherbarium[27882:303] CGImageGetWidth(image) : 5472
    2014-02-27 20:23:06.626 Wherbarium[27882:303] CGImageGetHeight(image) : 3648
    2014-02-27 20:23:06.627 Wherbarium[27882:303] CGImageGetBitsPerComponent(image) : 8
    2014-02-27 20:23:06.627 Wherbarium[27882:303] CGImageGetBytesPerRow(image) : 21888
    2014-02-27 20:23:06.627 Wherbarium[27882:303] CGImageGetColorSpace(image) : <CGColorSpace 0x618000032fc0> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1)
    2014-02-27 20:23:06.627 Wherbarium[27882:303] kCGBitmapByteOrderDefault : 0
    Feb 27 20:23:06 lorien Wherbarium[27882] <Error>: CGBitmapContextCreate: unsupported parameter combination: 8 integer bits/component; 24 bits/pixel; 3-component color space; kCGImageAlphaNone; 21888 bytes/row.

    J'ai essayé avec un fichier beaucoup plus petit, même erreur.


     


    J'affiche le fichier DSC00015.JPG à  l'écran dans mon application, je l'envoie sur un serveur PostgreSQL, je l'affiche dans une page html, je le récupère depuis PostgreSQL dans mon application, tout ça sans autre difficulté que la lenteur de ma connexion.


     


    Qu'est-ce cloche ?


  • CéroceCéroce Membre, Modérateur
    février 2014 modifié #16

    CGBitmapContextCreate() est assez capricieux quant aux formats d'image qu'il accepte. Ils sont décrits dans la doc de Core Graphics.



    Ce code fonctionne:



    + (CGContextRef) createBitmapContextWithWidth:(size_t)width height:(size_t)height
    {
    CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
    CGContextRef context = CGBitmapContextCreate(NULL, // data
    width, // width
    height, // height
    8, // bitsPerComponent
    width * 4, // bytesPerRow
    colorspace, // colorspace
    kCGImageAlphaNoneSkipLast); // bitmapInfo
    CGColorSpaceRelease(colorspace);

    return context; // Ne pas oublier d'appeler CGContextRelease()
    }

  • AliGatorAliGator Membre, Modérateur
    Je ne comprends plus rien : tu es reparti sur une solution pour réduire la résolution de l'image (largeur/hauteur) ? Je croyais que tu voulais réduire la qualité et la taille du fichier sans changer la taille de l'image ? La question / le but changé à  chaque post ?!
  • MalaMala Membre, Modérateur


    Qu'est-ce cloche ?




    L'absence de couche alpha.

  • Comme d'habitude, quand on a trouvé, ça paraà®t tout simple.


     


    Voici le code :



    CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)_photoData, NULL);
    CGImageRef image = CGImageSourceCreateImageAtIndex (imageSource, 0, NULL);
    size_t width = 1024;
    size_t height = width * CGImageGetHeight(image) / CGImageGetWidth(image);
    CGContextRef context = [Tmp createBitmapContextWithWidth:width height:height ];
    CGRect rect = {{0,0},{width,height}};
    CGContextDrawImage (context, rect, image);
    CGImageRef imageOut = CGBitmapContextCreateImage (context);

    // ******** écrire dans un fichier
    NSString *path = [@~/Pictures/Wherbarium/Photos/zozo.jpeg stringByExpandingTildeInPath];
    NSURL *NSURLdesktopURL = [[NSURL alloc] initFileURLWithPath:path];
    CGImageDestinationRef whereToSave = CGImageDestinationCreateWithURL((__bridge CFURLRef)NSURLdesktopURL, kUTTypeJPEG, 1, NULL);
    CGImageDestinationAddImage (whereToSave, imageOut, NULL);
    CGImageDestinationFinalize(whereToSave);
    // ******** écrire dans un NSData
    NSMutableData *miniData = [NSMutableData new];
    CGImageDestinationRef imgdst = CGImageDestinationCreateWithData ((__bridge CFMutableDataRef) miniData, kUTTypeJPEG, 1, NULL);
    CGImageDestinationAddImage (imgdst, imageOut, NULL);
    CGImageDestinationFinalize(imgdst);

    Dans Tmp, il y a la méthode de classe de Céroce, cf. #16.


     


    Il suffit de modifier width pour diminuer la taille du fichier : ici avec 1024, j'ai une bonne qualité d'affichage à  l'écran pour un fichier photo qui est passé de 7.4Mo à  306ko.


    J'ai exactement ce que je voulais.


     


    Je n'y serais pas arrivé sans votre aide.


     


    Très grand merci à  tous.


     


     


  • CéroceCéroce Membre, Modérateur

    T'as testé avec des images qui comportent une rotation EXIF ?


  • Si tu veux dire par là  des images venant d'un appareil photo ? Alors oui : mes images viennent d'un APN et contiennent bien des informations EXIF. Par contre elles semblent ne plus figurer dans la miniature, ce qui ne me gène pas outre mesure.


     


    Céroce a bien mentionné que ce n'est pas géré automatiquement par cgimagesource


     


    Je me propose de regarder ça plus tard, les rotations et les informations EXIF.

  • AliGatorAliGator Membre, Modérateur
    Non il veut dire par là  les images qui contiennent, dans leurs données EXIF, une info comme quoi une rotation est appliquée à  ton image.


    Certains appareils photos intègrent cette info de rotation quand ils détectent que l'appareil photo est vertical par exemple et pas horizontal pour indiquer la rotation de la photo, certes.


    Mais si tu ouvres une image via Aperçu et que tu fais un pomme-R pour appliquer une rotation à  droite de l'image puis que tu sauves le résultat tu auras aussi une image avec une rotation dans les données EXIF (l'image n'aura pas été tournée "en vrai" et les nouveaux pixels à  nouveau sauvés, il se sera contenté d'indiquer qu'il faut tourner l'image à  l'affichage en le sauvant dans les metadata EXIF)


    Si ta miniature n'a plus de données EXIF alors que ton original en avait et avait particulièrement cette info de rotation dedans qui du coup est perdue, alors y'a des chances que pour les images ayant cette donnée EXIF ta miniature sera pas dans le bon sens...


    Regarde ma catégorie UIImage+Resize sur mon GitHub (cf ma signature) tu verras comment mon code de génération de miniature traite ces rotations au cas par cas...
  • Bonjour


     


    Je reviens sur ma discussion.


     


    Build : OK


     


    exec : OK


     


    Mais si je fais xcode > product > analyse j'ai des massages que je ne comprends pas et je ne sais pas comment faire pour rectifier mon code pour les supprimer (cf. fichier attaché)


     


    Je ne suis pas familier avec les CG et CF...


     


    Merci de l'aide


     


     


     


  • AliGatorAliGator Membre, Modérateur
    août 2014 modifié #24


    Mais si je fais xcode > product > analyse j'ai des massages que je ne comprends pas et je ne sais pas comment faire pour rectifier mon code pour les supprimer (cf. fichier attaché)


    Je ne suis pas familier avec les CG et CF...

    A lire absolument : Memory Management Progamming Guide for CoreFoundation.


    En particulier il faut respecter la "Create Rule" (y'a un lien dédié à  ça dans l'article en question).


    Le principe est simple : toute méthode de CoreFoundation qui a "Create" ou "Copy" dans son nom retourne un objet dont tu as la responsabilité et sur lequel tu dois faire un CFRelease quand tu en as fini avec ledit objet.


    Donc chaque XXXCreate doit être balancé par son CFRelease correspondant. Ce qui n'est clairement pas le cas dans ton code et c'est ce que te signalent les warnings de ta capture.
  • J'obtiens de bon résultats avec cette méthode :


     


    NSData * UIImageJPEGRepresentation (

    UIImage *image,

    CGFloat compressionQuality

    );


     


    en faisant varier le coefficient compressionQuality.

  • AliGatorAliGator Membre, Modérateur
    août 2014 modifié #26
    Ah oui et au passage pour créer des miniatures efficacement avec ImageIO : il y a cet article de CocoaInTheShell qui explique une technique sympa. L'article date, mais la technique est toujours d'actualité.

    Réadapté avec ARC et le Modern Objective-C, ça devrait donner un truc dans le genre :

    - (UIImage*)thumbnailWithImageData:(NSData*)imageData maxSize:(CGFloat)maxSize
    {
    CGImageSourceRef src = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
    CFDictionaryRef options = (CFDictionaryRef)@{
    (id)kCGImageSourceCreateThumbnailWithTransform: (id)kCFBooleanTrue,
    (id)kCGImageSourceCreateThumbnailFromImageIfAbsent: (id)kCFBooleanTrue,
    (id)kCGImageSourceThumbnailMaxPixelSize: (id)@(maxSize)
    };
    CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex(src, 0, options); // Where the magic happens
    CFRelease(src); // cf. Create rule
    UIImage* img = [UIImage imageWithCGImage:thumbnail];
    CGImageRelease(thumbnail); // cf. Create rule
    return img;
    }
  • Je crois avoir compris - un peu


     


    Je n'ai plus d'erreur dans le "analyse"


     


    Merci à  tous


Connectez-vous ou Inscrivez-vous pour répondre.