Réduire la taille d'un NSBitmapImageRep

Bonjour à  tous,


 


Après quelques mois à  faire du son et de la musique, je reviens à  l'image.


 


Je modifie des images avec les méthodes de NSBitmapImageRep. Cela marche très bien, sauf que parfois, les images initiales sont tellement grandes que le temps de calcul est très long, et ce pour pas grand chose par rapport au fonctionnement du soft.


 


J'ai essayé de modifier la taille de la NSImage source :



[monImage setSize : NSMakeSize: //...]

mais la taille du NSBitmapImageRep produit à  partir de la NSImage est inchangée. C'est bien dit dans la notice, sauf que je n'ai pas trouver comment modifier les "ImageLength and ImageWidth attributes".


J'ai aussi tenté de jouer sur les valeurs de cette dernière :



monBitmapRep.pixelWidth = 300;

L'image s'affiche alors dans un coin de l'écran alors que le cadre (le rectangle) de la NSImage "post traitement" est bien déterminé (le reste du rectangle est rempli par une seule couleur).


 


Quelqu'un saurait peut-être comment faire? L'idéal serait de réduire l'image à  la source, lors de l'importation dans le projet.


 


Merci par avance.


Réponses

  • AliGatorAliGator Membre, Modérateur
    février 2014 modifié #2
    Ca fait longtemps que je n'ai pas fait de dev OSX et n'ai pas utilisé les NSBitmapImageRep, mais pour moi c'est une représentation existante d'une image, tu ne peux pas espérer modifier juste une propriété comme la pixelWidth par exemple et que ça te lance un calcul d'interpolation pour complètement recalculer toute la représentation de l'image (et avec quel type interpolation, d'ailleurs, comment tu lui préciserais ?). Je pense que ce n'est pas la bonne approche.

    Moi surtout si tu veux réduire l'image à  la source dès l'import dans le projet, je t'orienterais plutôt vers le framework "Image IO" qui est bien plus adapté / spécialisé dans la lecture de fichiers image, entre autres pour générer à  la volée des miniatures de l'image (sans être obligé de la décoder en entier dans sa totalité / taille réelle tout ça pour recalculer une interpolation à  une taille plus petite plus tard).

    D'autant qu'en général le format d'origine " comme le JPEG par exemple " est fait de telle manière que générer une miniature directement depuis la représentation d'origine peut se faire sans être obligé de tout décoder, mais en lisant que ce qui nous intéresse du fichier source (dans ton cas par exemple ça va sans doute juste lire les composantes principales de la transformée de Fourier JPEG plutôt que de lire tout le fichier).
    Donc autant le faire directement à  la source " au moment de lire le fichier, dire qu'on ne veut qu'une miniature " plutôt que de lire la source complète (l'image HD) et la réduire après (gaspillage de mémoire, de processeur, de calcul de décodage...)
  • Merci AliGator,


     


    Je vais regarder par là . Il me faut un peu plus qu'une miniature, mais réduire un jpeg de 2 ou 3 Mo (ou plus) à  400Ko serait très bien.


  • AliGatorAliGator Membre, Modérateur
    Comme indiqué dans le lien, le terme "miniature" est très générique vu que tu précises la taille donc ça peut être une "miniature" de 800x800 ;)
  • Finalement, je suis passé par Core Image que je connaissais déjà . Résolu! Merci encore.


  • MalaMala Membre, Modérateur

    Cela me semble vraiment peu optimal pour une simple retaille: gourmand en ressources et peu performant. Je serais curieux de voir un comparatif avec la solution proposée en lien par Ali. CoreImage impose un transfert complet de l'image vers le GPU pour effectuer une retaille qui est dans les faits un calcul assez simple.


     


    A noter aussi que CoreImage est limité à  l'exécution par les possibilités de la carte graphique (les dimensions ne peuvent dépasser celle de la taille max d'une texture OpenGL). On peut donc avoir des surprises avec de gros fichiers sur de vieilles machines.


  • AliGatorAliGator Membre, Modérateur
    février 2014 modifié #7
    Mouais... Utiliser CoreImage pour charger une version réduite d'une image :
    • C'est clairement de l'Overkill !! CoreImage c'est bien mais c'est lourd, un peu l'usine à  gaz surtout pour ce que tu veux faire (réduire une image) !!
    • Ca t'oblige à  charger en mémoire l'image taille réelle avant de pouvoir ensuite la réduire. Ainsi une image de 2Mo tu vas devoir charger les 2Mo de données binaires (genre le flux JPEG par exemple) de l'image ne mémoire + décoder ce flux binaire et obtenir l'image (l'ensemble des pixels reconstruits), NSImage qui va faire encore plusieurs Mo en mémoire (autant de N = largeur*hauteur*nbBitsPerPixels/8, attention aux limites max de tailles de VRAM et des buffers du GPU...) + calculer enfin la réduction de cette image pour regénérer une autre NSImage. En bref tu vas avoir un gros pic de 2Mo + N Mo de mémoire de consommée le temps de calculer la version réduire de l'image, petit gaspillage de mémoire à  l'horizon (heureusement que tu n'es pas sur device mobile, ça serait violent !!)
    • Alors qu'avec des outils comme ImageIO ça fais la réduction à  la source en ne chargeant / lisant que le nécessaire du fichier / flux binaire JPEG. Résultat : des outils adaptés (et pas l'overkill d'un lourd framework comme CoreData) et une empreinte mémoire bien plus réduite !
    La avec CoreImage c'est un peu comme si tu partais d'un gros bloc de bois massif et que tu taillais dedans au ciseau à  bois pour faire une niche pour ton chien... ça gaspille et c'est un peu violent juste pour ça alors que ça paraà®t + simple et plus raisonnable de partir de planches prédécoupées et juste les assembler :D

    [EDIT] Grillé par Mala
  • Ah, alors là , je tombe de haut!! La méthode marche et le résultat est immédiat. J'en étais resté là ! 


     


    Je vous montre ce que j'ai fait. Ne hurlez pas surtout!!   :))



    - (void)setImageDeFond:(NSImage *)i
    {
    [i retain];
    [imageDeFond release];

    NSBitmapImageRep *bipIm = [[NSBitmapImageRep alloc] initWithData:[i TIFFRepresentation]];
    if ((bipIm.pixelsWide > 600) || (bipIm.pixelsHigh > 600)){

    float coeffReduction;
    float maxRo = MAX((1.0 * bipIm.pixelsWide),(1.0 * bipIm.pixelsHigh));
    coeffReduction = 600 / maxRo;
    context = [[NSGraphicsContext currentContext] CIContext];
    NSRect refondu = NSMakeRect(0.0, 0.0, (i.size.width*coeffReduction), (i.size.height*coeffReduction));

    if (iCopiePourReduction)[iCopiePourReduction release];

    iCopiePourReduction = [[[CIImage alloc] initWithData:[i TIFFRepresentation]]retain];
    iCopiePourReduction2b = [iCopiePourReduction imageByApplyingTransform:CGAffineTransformMakeScale (coeffReduction, coeffReduction)];
    NSCIImageRep *irP = [NSCIImageRep imageRepWithCIImage:iCopiePourReduction2b];

    NSImage *futureImage = [[[NSImage alloc] initWithSize:
    refondu.size]autorelease];
    [futureImage addRepresentation:irP];
    imageDeFond = [[NSImage alloc]initWithData:[futureImage TIFFRepresentation]];
    [imageDeFond retain];

    }

    else {
    imageDeFond = i;
    }
    [bipIm release];

    [self setNeedsDisplay:YES];
    [i release];

    [self setImageHarmonisee:nil];
    [self setImageDeFondHarmonisee:NO];
    }

    ça marche!! ça marche!!  :)))


  • AliGatorAliGator Membre, Modérateur
    Wow c'est encore pire que ce que je pensais !!! Ou pourquoi faire simple quand on peut faire compliqué !!!


    Pourquoi utiliser CoreImage pour si peu alors qu'à  ce stade vu que ton code à  déjà  la NSImage d'origine full-size et que tu as un NSBitmapContext déjà  dans ton code, il suffit de faire un drawInRect de l'image dans le contexte et basta ?!


    Si tu as déjà  l'image pleine taille chargée en mémoire c'est pas dur de la redimensionner. Inutile de passer par des NSBitmapImageRep pour ça (quel intérêt ?) et encore moins et surtout pas par CoreImage pour un si bête redimensionnement ! Suffit de la dessiner plus petite dans un Context et ça suffit.


    Mais la question d'origine que tu as posée laissait croire que tu voulais optimiser ça et faire le redimensionnement au moment d'ouvrir le fichier image, et non pas si tard une fois le fichier full-size déjà  décodé et lu et charge en mémoire !

    La tu fais tout l'inverse, ton code fait limite le pire cas qui soit alors que la question d'origine laissait présager que tu voulais au contraire optimiser les choses... du coup je comprend plus rien à  la question vu que le code que tu as pondu répond de façon opposée !
  • L'image est ensuite utilisée avec différents algorithmes. Pour que le temps de traitement ne soit pas trop long (d'où la limitation à  600 pixels max), et étant donné que le rendu est suffisant, je souhaite optimiser lors de l'importation de l'image sa dimension.


     


    Ensuite, j'ai fait avec ce que je connaissais... Bon, je vais essayer de comprendre ImageIO.


  • AliGatorAliGator Membre, Modérateur
    février 2014 modifié #11

    L'image est ensuite utilisée avec différents algorithmes. Pour que le temps de traitement ne soit pas trop long (d'où la limitation à  600 pixels max), et étant donné que le rendu est suffisant, je souhaite optimiser lors de l'importation de l'image sa dimension.

    Oui j'avais très bien compris la demande. Et c'est pour ça que je t'ai donné la solution avec ImageIO, puisque c'est ce qui répondait le mieux à  ta question, en optimisant dès l'importation de l'image (c'est tout l'avantage d'ImageIO).

    ImageIO c'est la solution la plus optimale, puisque tu vas même jusqu'à  éviter de lire l'image complète en mémoire pour générer l'image en taille réduire. Donc c'est le must côté optimisation et utilisation mémoire.
    Mais ce n'est pas la seule solution, d'autres existent, moins optimisées (car ça importe d'abord toute l'image avant de la réduire dans un 2e temps) mais sans doute plus connues et directement en Objective-C (et non en C pur comme ImageIO, même si vu que tu n'as qu'à  copier/coller le code fourni par la doc ça n'a rien de sorcier au final...)

    Ensuite, j'ai fait avec ce que je connaissais... Bon, je vais essayer de comprendre ImageIO.

    Mais même si tu ne connaissais pas ImageIO, il y a des méthodes bien plus simples que de sortir la grosse artillerie avec CoreImage. Genre juste en utilisant CoreGraphics et en faisant un "drawInRect:" de ton image dans un CGBitmapContext de taille plus petite. Tout cela est comme d'habitude expliqué en détail dans le Programming Guide dédié, avec tous les concepts liés.

    Donc soit tu utilises une solution en Objective-C parce que ImageIO te fais peur et que tu n'aimes pas le C pur, mais ça sera pas optimisé dès la source " et dans ce cas prend quand même une solution simple (genre CoreGraphics qui est fait pour ça) et pas un bulldozer comme CoreImage (qui est plutôt fait pour les effets et le traitement d'image que pour du simple redimentionnement)... Soit tu choisis ImageIO car ça optimise à  la source, dès l'importation de l'image (dès la lecture du fichier image en mémoire), certes c'est du C et pas de l'ObjC mais en même temps tu n'as qu'à  recopier le code fourni...
  • Bonjour.


     


    Résumé du projet : un client osx pour gérer des images stockées dans une base de données d'un serveur PostgreSQL distant.


    Problème : les images peuvent être lourdes, jusqu'à  dix Mo voire plus, donc grande lenteur de transfert des images.


     


    L'idée est donc d'associer à  chaque image une miniature qui sera utilisée à  la place de l'image pour accélérer les manipulations, réservant l'usage des images elles-mêmes lorsque c'est nécessaire.


     


    ImageIO paraà®t répondre à  mon besoin, mais ... ImageIO me semble travailler uniquement à  partir de fichiers en entrée ou en sortie. Ce serait possible, mais ça multiplierait les entrées/sorties. 


     


    Est-il possible avec ImageIO d'avoir des NSData à  la place des fichiers ? 


     


    Merci


  • AliGatorAliGator Membre, Modérateur
    février 2014 modifié #13
    J'ai jamais trop fait mumuse avec ImageIO en pratique, du coup je suis allé voir sur le "Image I/O Programming Guide" et la première chose qu'il disent dès la première ligne c'est "The Image I/O framework provides opaque data types for reading image data from a source (CGImageSourceRef) and writing image data to a destination (CGImageDestinationRef)."

    Donc là  ils te disent que les sources d'images en entrée sont représentées par des CGImageSourceRef. Tu cliques sur le lien et tu atterris sur la doc de CGImageSourceRef où à  peine commencé tu vois qu'il existe une fonction "CGImageSourceCreateWithData" dont le nom parle d'elle-même.

    Conclusion : après 30s de recherche pour moi la réponse est oui ;)
  • Ca marche ... presque.


    J'arrive à  afficher une miniature, mais pas à  l'écrire à  une NSURL;


     


    Je bloque sur "Working with Image Destinations".


    Dans la documentation :



    float compression = 1.0; // Lossless compression if available.
    int orientation = 4; // Origin is at bottom, left.
    CFStringRef myKeys[3];
    CFTypeRef myValues[3];
    CFDictionaryRef myOptions = NULL;
    myKeys[0] = kCGImagePropertyOrientation;
    myValues[0] = CFNumberCreate(NULL, kCFNumberIntType, &orientation);
    myKeys[1] = kCGImagePropertyHasAlpha;
    myValues[1] = kCFBooleanTrue;
    myKeys[2] = kCGImageDestinationLossyCompressionQuality;
    myValues[2] = CFNumberCreate(NULL, kCFNumberFloatType, &compression);
    myOptions = CFDictionaryCreate( NULL, (const void **)myKeys, (const void **)myValues, 3,
    &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

    - (void) writeCGImage: (CGImageRef) image toURL: (NSURL*) url withType: (CFStringRef) imageType andOptions: (CFDictionaryRef) options
    {
    CGImageDestinationRef myImageDest = CGImageDestinationCreateWithURL((CFURLRef)url, imageType, 1, nil);
    CGImageDestinationAddImage(myImageDest, image, options);
    CGImageDestinationFinalize(myImageDest);
    CFRelease(myImageDest);
    }

    J'essaye d'utiliser writeCGImage avec l'appel :



    [self writeCGImage:miniature toURL:url withType:myKeys andOptions:myOptions];

    A la compilation, j'ai l'erreur



    Incompatible pointer types sending 'CFStringRef [3]' to parameter of type 'CFStringRef' (aka 'const struct __CFString *')

    Ce n'est pas trop surprenant, puisque myKeys est un tableau et que le paramètre withType n'en est pas un ?


     


    Comment faire ? 


     


     


     


     


     


  • AliGatorAliGator Membre, Modérateur
    Bah CGImageDestionationCreateWithURL ça prend en 2e paramètre (i.e. ton paramètre "imageType") un Uniform Type Identifier (UTI), comme "image.jpeg" ou un truc comme ça.

    Pourquoi tu lui passerais ton tableau "myKeys" en paramètre, qui n'a rien à  voir avec la choucroute et n'est pas du tout un UTI ?
  • mybofymybofy Membre
    février 2014 modifié #16

    Il suffit de supprimer de donner le bon paramètre : public.jpeg et non public.jpg •..


     


    Ca marche ... presque.


     


    Impossible d'avoir une miniature de taille supérieure à  160.



    int imageSize = 100000;
    miniature = MyCreateThumbnailImageFromData(_photoData, imageSize);

    Même largeur et hauteur, dès que imageSize > 160 et quelle que soit la taille du fichier jpg d'origine.


     


    J'utilise la fonction MyCreateThumbnailImageFromData de la doc ImageIO.


    J'ai mis  3 (au lieu de 2) dans le CFDictionaryCreate pensant que la dimension serait prise en compte, mais en fait elle ne l'est pas.


     


    Je ne comprends pas à  quoi sert imageSize ?


  • AliGatorAliGator Membre, Modérateur

    Je ne comprends pas à  quoi sert imageSize ?

    Bah heu à  donner la taille maximum de ton thumbnail, non ?

    Genre si tu passes 160 comme valeur pour imageSize, si ton image d'origine a une des dimensions (largeur ou hauteur) qui fait + de 160px alors il va faire une miniature de 160px
  • J'ai bien compris pour le imageSize.


     


    Je voudrais une miniature dont la plus grande des deux dimensions soit exactement 256px.


     


    Comment faire ?


  • AliGatorAliGator Membre, Modérateur
    ??? Bah heu imageSize = 256 donc, non ? C'est quoi que tu comprends pas ?
  • imageSize=100 : résultat miniature width=100 height=75 (1)



    imageSize=160 : résultat miniature width=160 height=120 (2)



    imageSize=1000 : résultat miniature width=160 height=120 (3)


    imageSize=10000 : résultat miniature width=160 height=120 (4)


     


    (1) marche comme prévu


    (2) marche comme prévu 


    (3) MyCreateThumbnailImageFromData refuse de créer une miniature de taille supérieure à  160x160 !


    (4) idem


     


    Cette taille limite par défaut ne semble indiquée nulle part.


    A moins qu'il existe un moyen d'imposer la taille exacte : si oui je ne l'ai pas trouvé.


     


  • AliGatorAliGator Membre, Modérateur
    bugreport.apple.com ?

    google.fr ?

    stackoverflow.com ?
  • Bon, j'abandonne l'idée de ImageIO.


    Et j'ouvre une nouvelle discussion.


     


    Merci.


  • colas_colas_ Membre
    février 2014 modifié #23

    Pourquoi n'utilises-tu pas 


    CGImageSourceCreateImageAtIndex (ou similaire)


    au lieu de


    CGImageSourceCreateThumbnailAtIndex (ou similaire) ?


    cf https://developer.apple.com/library/ios/documentation/graphicsimaging/Reference/CGImageSource/Reference/reference.html#//apple_ref/c/func/CGImageSourceCreateThumbnailAtIndex


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