web-dev-qa-db-fra.com

Fichier libre verrouillé par un nouveau bitmap (filePath)

J'ai l'image d'un PictureBox pointant vers un certain fichier "A". Au moment de l'exécution, je souhaite modifier l'image de la PictureBox par une autre "B", mais l'erreur suivante apparaît: 

"Une exception de la première chance du type 'System.IO.IOException' s'est produite dans mscorlib.dll Informations supplémentaires: Le processus ne peut pas accéder au fichier" A "car il est utilisé par un autre processus."

Je règle l'image comme suit:

pbAvatar.Image = new Bitmap(filePath);

Comment puis-je déverrouiller le premier fichier?

36
MrCatacroquer

L'utilisation d'un filestream déverrouillera le fichier une fois qu'il aura été lu et éliminé:

using (var fs = new System.IO.FileStream("c:\\path to file.bmp", System.IO.FileMode.Open))
{
    var bmp = new Bitmap(fs);
    pct.Image = (Bitmap) bmp.Clone();
}

Edit: Mise à jour pour permettre la suppression du bitmap d'origine et la fermeture du FileStream.

CETTE REPONSE IS PAS DE SECURITE - Voir les commentaires et voir la discussion dans la réponse de net_prog . La modification à utiliser Clone ne le rend pas plus sûr. Cloner tous les champs, y compris la référence filestream, ce qui, dans certains cas, posera problème.

25
Pondidum

Voici mon approche pour ouvrir une image sans verrouiller le fichier ...

public static Image FromFile(string path)
{
    var bytes = File.ReadAllBytes(path);
    var ms = new MemoryStream(bytes);
    var img = Image.FromStream(ms);
    return img;
}

MISE À JOUR: J'ai fait quelques tests de performances pour voir quelle méthode était la plus rapide. Je l'ai comparé à @net_progs "copy from bitmap" answer (qui semble être le plus proche des corrects, bien qu'il y ait quelques problèmes) J'ai chargé l'image 10000 fois pour chaque méthode et calculé le temps moyen par image. Voici les résultats:

Loading from bytes: ~0.26 ms per image.
Copying from bitmap: ~0.50 ms per image.

Les résultats semblent logiques puisque vous devez créer deux fois l’image en utilisant la méthode copy from bitmap.

UPDATE: Si vous avez besoin d’une carte bitmap, vous pouvez faire:

return (Bitmap)Image.FromStream(ms);
48
Brian

C’est une question de verrouillage commune qui fait l’objet de nombreuses discussions sur le Web.

L'astuce suggérée avec stream ne fonctionnera pas, elle fonctionnera initialement mais causera des problèmes plus tard. Par exemple, il chargera l'image et le fichier restera déverrouillé, mais si vous essayez d'enregistrer l'image chargée via la méthode Save (), une exception générique GDI + sera générée.

Ensuite, le chemin avec la réplication par pixel ne semble pas être solide, du moins c'est bruyant.

Ce que j'ai trouvé efficace est décrit ici: http://www.eggheadcafe.com/Microsoft/Csharp/35017279/imagefromfile--locks-file.aspx

Voici comment l'image devrait être chargée:

Image img;
using (var bmpTemp = new Bitmap("image_file_path"))
{
    img = new Bitmap(bmpTemp);
}

Je cherchais une solution à ce problème et cette méthode fonctionnait bien pour moi jusqu'à présent. J'ai donc décidé de la décrire car j'ai constaté que de nombreuses personnes conseillent une approche de flux incorrecte ici et sur le Web.

38
net_prog

Vous ne pouvez pas supprimer/fermer un flux tant qu'un objet bitmap l'utilise encore. (Déterminer si l’objet bitmap aura besoin d’y avoir accès à nouveau n’est déterminant que si vous savez quel type de fichier vous utilisez et quelles opérations vous allez exécuter. - par exemple, pour les images au format SOME .gif, le flux est fermé avant le constructeur revient.)

Clone crée une "copie exacte" du bitmap (par documentation; ILSpy l'appelle en appelant des méthodes natives, il est donc trop compliqué à repérer pour le moment), probablement qu'il copie également les données Stream - sinon ce ne serait pas un fichier. Copie exacte.

Votre meilleur pari est de créer une réplique de l'image au pixel près - bien que YMMV (avec certains types d'images, il peut y avoir plus d'un cadre, ou vous devrez peut-être aussi copier les données de la palette.) Mais cela fonctionne pour la plupart des images. :

static Bitmap LoadImage(Stream stream)
{
    Bitmap retval = null;

    using (Bitmap b = new Bitmap(stream))
    {
        retval = new Bitmap(b.Width, b.Height, b.PixelFormat);
        using (Graphics g = Graphics.FromImage(retval))
        {
            g.DrawImage(b, Point.Empty);
            g.Flush();
        }
    }

    return retval;
}

Et alors vous pouvez l'invoquer comme ceci:

using (Stream s = ...)
{
    Bitmap x = LoadImage(s);
}
6
BrainSlugs83

Voici la technique que j'utilise actuellement et qui semble fonctionner le mieux. Il présente l'avantage de produire un objet Bitmap avec le même format de pixel (24 bits ou 32 bits) et la même résolution (72 dpi, 96 dpi, quel que soit) par rapport au fichier source.

  // ImageConverter object used to convert JPEG byte arrays into Image objects. This is static 
  //  and only gets instantiated once.
  private static readonly ImageConverter _imageConverter = new ImageConverter();

Ceci peut être utilisé aussi souvent que nécessaire, comme suit:

     Bitmap newBitmap = (Bitmap)_imageConverter.ConvertFrom(File.ReadAllBytes(fileName));

Edit: Voici une mise à jour de la technique ci-dessus: https://stackoverflow.com/a/16576471/253938

4
RenniePet

( La réponse acceptée est fausse. Lorsque vous essayez de LockBits(...) sur le bitmap cloné, vous rencontrerez éventuellement des erreurs GDI +.)


Je ne vois que 3 façons de sortir de ça:

  • copiez votre fichier dans un fichier temporaire et ouvrez-le simplement new Bitmap(temp_filename)
  • ouvrez votre fichier, lisez l'image, créez une copie pixel-size-pixelformat (ne pas Clone()) et supprimez le premier bitmap
  • (accepte la fonctionnalité de fichier verrouillé)
3
Bitterblue

Pour autant que je sache, cela est sûr à 100%, car l'image résultante est créée à 100% en mémoire, sans aucune ressource liée, et sans aucun flux ouvert en mémoire. Il agit comme n'importe quelle autre variable Bitmap créée à partir d'un constructeur qui ne spécifie aucune source d'entrée et, contrairement à certaines des autres réponses ici, il conserve le format de pixel d'origine, ce qui signifie qu'il peut être utilisé sur des formats indexés.

Basé sur cette réponse , mais avec des correctifs supplémentaires et sans importation de bibliothèque externe.

/// <summary>
/// Clones an image object to free it from any backing resources.
/// Code taken from http://stackoverflow.com/a/3661892/ with some extra fixes.
/// </summary>
/// <param name="sourceImage">The image to clone</param>
/// <returns>The cloned image</returns>
public static Bitmap CloneImage(Bitmap sourceImage)
{
    Rectangle rect = new Rectangle(0, 0, sourceImage.Width, sourceImage.Height);
    Bitmap targetImage = new Bitmap(rect.Width, rect.Height, sourceImage.PixelFormat);
    targetImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);
    BitmapData sourceData = sourceImage.LockBits(rect, ImageLockMode.ReadOnly, sourceImage.PixelFormat);
    BitmapData targetData = targetImage.LockBits(rect, ImageLockMode.WriteOnly, targetImage.PixelFormat);
    Int32 actualDataWidth = ((Image.GetPixelFormatSize(sourceImage.PixelFormat) * rect.Width) + 7) / 8;
    Int32 h = sourceImage.Height;
    Int32 origStride = sourceData.Stride;
    Boolean isFlipped = origStride < 0;
    origStride = Math.Abs(origStride); // Fix for negative stride in BMP format.
    Int32 targetStride = targetData.Stride;
    Byte[] imageData = new Byte[actualDataWidth];
    IntPtr sourcePos = sourceData.Scan0;
    IntPtr destPos = targetData.Scan0;
    // Copy line by line, skipping by stride but copying actual data width
    for (Int32 y = 0; y < h; y++)
    {
        Marshal.Copy(sourcePos, imageData, 0, actualDataWidth);
        Marshal.Copy(imageData, 0, destPos, actualDataWidth);
        sourcePos = new IntPtr(sourcePos.ToInt64() + origStride);
        destPos = new IntPtr(destPos.ToInt64() + targetStride);
    }
    targetImage.UnlockBits(targetData);
    sourceImage.UnlockBits(sourceData);
    // Fix for negative stride on BMP format.
    if (isFlipped)
        targetImage.RotateFlip(RotateFlipType.Rotate180FlipX);
    // For indexed images, restore the palette. This is not linking to a referenced
    // object in the original image; the getter of Palette creates a new object when called.
    if ((sourceImage.PixelFormat & PixelFormat.Indexed) != 0)
        targetImage.Palette = sourceImage.Palette;
    // Restore DPI settings
    targetImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);
    return targetImage;
}

Pour appeler, utilisez simplement:

/// <summary>Loads an image without locking the underlying file.</summary>
/// <param name="path">Path of the image to load</param>
/// <returns>The image</returns>
public static Bitmap LoadImageSafe(String path)
{
    using (Bitmap sourceImage = new Bitmap(path))
    {
        return CloneImage(sourceImage);
    }
}

Ou, à partir d'octets:

/// <summary>Loads an image from bytes without leaving open a MemoryStream.</summary>
/// <param name="fileData">Byte array containing the image to load.</param>
/// <returns>The image</returns>
public static Bitmap LoadImageSafe(Byte[] fileData)
{
    using (MemoryStream stream = new MemoryStream(fileData))
    using (Bitmap sourceImage = new Bitmap(stream))    {
    {
        return CloneImage(sourceImage);
    }
}
1
Nyerguds

Lisez-le dans le flux, créez un bitmap, fermez le flux.

0
Sasha Reminnyi