prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Reading PNG/JPG images into a torch::tensor and saving a torch::tensor to PNG/JPG in C++ without OpenCV 
  ## 🚀 Feature
  
  After integrating Siv3D with Libtorch (https://github.com/QuantScientist/Siv3DTorch) I am now trying to read and write images from and to Siv3D **in C++, not Python**. The way it works is:
  
  ## Motivation
  In C++ I need to do the following:
  1. An image is read from disk (usually using OpenCV which is easy but I am trying to avoid)
  2. The image is converted to torch::tensor
  3. A DL model is applied on the tensor
  4. A tensor is returned from the model
  5. The tensor is converted to an image for display/saving purposes.
  
  This is one example where they used stb_image to this, avoiding the use of OpenCV.
  https://github.com/prabhuomkar/pytorch-cpp/blob/master/utils/image_io/src/image_io.cpp
  
  ## Pitch
  
  ## Alternatives
  
  For reference this is the OpenCV to Libtorch conversion utils which I use, I would like something very similiar:
  ```
  at::Tensor matToTensor(cv::Mat frame, int h, int w, int c) {
      cv::cvtColor(frame, frame, CV_BGR2RGB);
      frame.convertTo(frame, CV_32FC3, 1.0f / 255.0f);
      auto input_tensor = torch::from_blob(frame.data, {1, h, w, c});
      input_tensor = input_tensor.permute({0, 3, 1, 2});
  
      torch::DeviceType device_type = torch::kCPU;
  //    if (torch::cuda::is_available()) {
      device_type = torch::kCUDA;
  //    }
      input_tensor = input_tensor.to(device_type);
      return input_tensor;
  }
  
  cv::Mat tensorToOpenCv(at::Tensor out_tensor, int h, int w, int c) {
      out_tensor = out_tensor.squeeze().detach().permute({1, 2, 0});
      out_tensor = out_tensor.mul(255).clamp(0, 255).to(torch::kU8);
      out_tensor = out_tensor.to(torch::kCPU);
      cv::Mat resultImg(h, w, CV_8UC3);
      // cv::Mat resultImg(h, w, CV_8UC1);
      std::memcpy((void *) resultImg.data, out_tensor.data_ptr(), sizeof(torch::kU8) * out_tensor.numel());
      return resultImg;
  }
  
  ```
  ## Additional context
  
  I found this:"https://github.com/pytorch/vision/blob/5e4a9f6d1a2bf85137f4826dbf76e4f25986f878/torchvision/csrc/cpu/image/readpng_cpu.cpp
  however, could not get any useful method out of it. 
  
  Thanks, 
  
  
  
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `torchvision/io/image.py`
  Content:
  ```
  1 import torch
  2 
  3 import os
  4 import os.path as osp
  5 import importlib.machinery
  6 
  7 _HAS_IMAGE_OPT = False
  8 
  9 try:
  10     lib_dir = osp.join(osp.dirname(__file__), "..")
  11 
  12     loader_details = (
  13         importlib.machinery.ExtensionFileLoader,
  14         importlib.machinery.EXTENSION_SUFFIXES
  15     )
  16 
  17     extfinder = importlib.machinery.FileFinder(lib_dir, loader_details)  # type: ignore[arg-type]
  18     ext_specs = extfinder.find_spec("image")
  19     if ext_specs is not None:
  20         torch.ops.load_library(ext_specs.origin)
  21         _HAS_IMAGE_OPT = True
  22 except (ImportError, OSError):
  23     pass
  24 
  25 
  26 def decode_png(input: torch.Tensor) -> torch.Tensor:
  27     """
  28     Decodes a PNG image into a 3 dimensional RGB Tensor.
  29     The values of the output tensor are uint8 between 0 and 255.
  30 
  31     Arguments:
  32         input (Tensor[1]): a one dimensional int8 tensor containing
  33     the raw bytes of the PNG image.
  34 
  35     Returns:
  36         output (Tensor[3, image_height, image_width])
  37     """
  38     if not isinstance(input, torch.Tensor) or input.numel() == 0 or input.ndim != 1:  # type: ignore[attr-defined]
  39         raise ValueError("Expected a non empty 1-dimensional tensor.")
  40 
  41     if not input.dtype == torch.uint8:
  42         raise ValueError("Expected a torch.uint8 tensor.")
  43     output = torch.ops.image.decode_png(input)
  44     return output
  45 
  46 
  47 def read_png(path: str) -> torch.Tensor:
  48     """
  49     Reads a PNG image into a 3 dimensional RGB Tensor.
  50     The values of the output tensor are uint8 between 0 and 255.
  51 
  52     Arguments:
  53         path (str): path of the PNG image.
  54 
  55     Returns:
  56         output (Tensor[3, image_height, image_width])
  57     """
  58     if not os.path.isfile(path):
  59         raise ValueError("Expected a valid file path.")
  60 
  61     size = os.path.getsize(path)
  62     if size == 0:
  63         raise ValueError("Expected a non empty file.")
  64     data = torch.from_file(path, dtype=torch.uint8, size=size)
  65     return decode_png(data)
  66 
  67 
  68 def decode_jpeg(input: torch.Tensor) -> torch.Tensor:
  69     """
  70     Decodes a JPEG image into a 3 dimensional RGB Tensor.
  71     The values of the output tensor are uint8 between 0 and 255.
  72     Arguments:
  73         input (Tensor[1]): a one dimensional int8 tensor containing
  74     the raw bytes of the JPEG image.
  75     Returns:
  76         output (Tensor[3, image_height, image_width])
  77     """
  78     if not isinstance(input, torch.Tensor) or len(input) == 0 or input.ndim != 1:  # type: ignore[attr-defined]
  79         raise ValueError("Expected a non empty 1-dimensional tensor.")
  80 
  81     if not input.dtype == torch.uint8:
  82         raise ValueError("Expected a torch.uint8 tensor.")
  83 
  84     output = torch.ops.image.decode_jpeg(input)
  85     return output
  86 
  87 
  88 def read_jpeg(path: str) -> torch.Tensor:
  89     """
  90     Reads a JPEG image into a 3 dimensional RGB Tensor.
  91     The values of the output tensor are uint8 between 0 and 255.
  92     Arguments:
  93         path (str): path of the JPEG image.
  94     Returns:
  95         output (Tensor[3, image_height, image_width])
  96     """
  97     if not os.path.isfile(path):
  98         raise ValueError("Expected a valid file path.")
  99 
  100     size = os.path.getsize(path)
  101     if size == 0:
  102         raise ValueError("Expected a non empty file.")
  103     data = torch.from_file(path, dtype=torch.uint8, size=size)
  104     return decode_jpeg(data)
  105
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/torchvision/io/image.py b/torchvision/io/image.py
  --- a/torchvision/io/image.py
  +++ b/torchvision/io/image.py
  @@ -102,3 +102,42 @@
           raise ValueError("Expected a non empty file.")
       data = torch.from_file(path, dtype=torch.uint8, size=size)
       return decode_jpeg(data)
  +
  +
  +def encode_jpeg(input: torch.Tensor, quality: int = 75) -> torch.Tensor:
  +    """
  +    Takes an input tensor in CHW layout (or HW in the case of grayscale images)
  +    and returns a buffer with the contents of its corresponding JPEG file.
  +    Arguments:
  +        input (Tensor[channels, image_height, image_width]): int8 image tensor
  +    of `c` channels, where `c` must be 1 or 3.
  +        quality (int): Quality of the resulting JPEG file, it must be a number
  +    between 1 and 100. Default: 75
  +    Returns
  +        output (Tensor[1]): A one dimensional int8 tensor that contains the raw
  +    bytes of the JPEG file.
  +    """
  +    if quality < 1 or quality > 100:
  +        raise ValueError('Image quality should be a positive number '
  +                         'between 1 and 100')
  +
  +    output = torch.ops.image.encode_jpeg(input, quality)
  +    return output
  +
  +
  +def write_jpeg(input: torch.Tensor, filename: str, quality: int = 75):
  +    """
  +    Takes an input tensor in CHW layout (or HW in the case of grayscale images)
  +    and saves it in a JPEG file.
  +    Arguments:
  +        input (Tensor[channels, image_height, image_width]): int8 image tensor
  +    of `c` channels, where `c` must be 1 or 3.
  +        filename (str): Path to save the image.
  +        quality (int): Quality of the resulting JPEG file, it must be a number
  +    between 1 and 100. Default: 75
  +    """
  +    if quality < 1 or quality > 100:
  +        raise ValueError('Image quality should be a positive number '
  +                         'between 1 and 100')
  +
  +    torch.ops.image.write_jpeg(input, filename, quality)
  
verification_info: {"golden_diff": "diff --git a/torchvision/io/image.py b/torchvision/io/image.py\n--- a/torchvision/io/image.py\n+++ b/torchvision/io/image.py\n@@ -102,3 +102,42 @@\n         raise ValueError(\"Expected a non empty file.\")\n     data = torch.from_file(path, dtype=torch.uint8, size=size)\n     return decode_jpeg(data)\n+\n+\n+def encode_jpeg(input: torch.Tensor, quality: int = 75) -> torch.Tensor:\n+    \"\"\"\n+    Takes an input tensor in CHW layout (or HW in the case of grayscale images)\n+    and returns a buffer with the contents of its corresponding JPEG file.\n+    Arguments:\n+        input (Tensor[channels, image_height, image_width]): int8 image tensor\n+    of `c` channels, where `c` must be 1 or 3.\n+        quality (int): Quality of the resulting JPEG file, it must be a number\n+    between 1 and 100. Default: 75\n+    Returns\n+        output (Tensor[1]): A one dimensional int8 tensor that contains the raw\n+    bytes of the JPEG file.\n+    \"\"\"\n+    if quality < 1 or quality > 100:\n+        raise ValueError('Image quality should be a positive number '\n+                         'between 1 and 100')\n+\n+    output = torch.ops.image.encode_jpeg(input, quality)\n+    return output\n+\n+\n+def write_jpeg(input: torch.Tensor, filename: str, quality: int = 75):\n+    \"\"\"\n+    Takes an input tensor in CHW layout (or HW in the case of grayscale images)\n+    and saves it in a JPEG file.\n+    Arguments:\n+        input (Tensor[channels, image_height, image_width]): int8 image tensor\n+    of `c` channels, where `c` must be 1 or 3.\n+        filename (str): Path to save the image.\n+        quality (int): Quality of the resulting JPEG file, it must be a number\n+    between 1 and 100. Default: 75\n+    \"\"\"\n+    if quality < 1 or quality > 100:\n+        raise ValueError('Image quality should be a positive number '\n+                         'between 1 and 100')\n+\n+    torch.ops.image.write_jpeg(input, filename, quality)\n", "issue": "Reading PNG/JPG images into a torch::tensor and saving a torch::tensor to PNG/JPG in C++ without OpenCV \n## \ud83d\ude80 Feature\r\n\r\nAfter integrating Siv3D with Libtorch (https://github.com/QuantScientist/Siv3DTorch) I am now trying to read and write images from and to Siv3D **in C++, not Python**. The way it works is:\r\n\r\n## Motivation\r\nIn C++ I need to do the following:\r\n1. An image is read from disk (usually using OpenCV which is easy but I am trying to avoid)\r\n2. The image is converted to torch::tensor\r\n3. A DL model is applied on the tensor\r\n4. A tensor is returned from the model\r\n5. The tensor is converted to an image for display/saving purposes.\r\n\r\nThis is one example where they used stb_image to this, avoiding the use of OpenCV.\r\nhttps://github.com/prabhuomkar/pytorch-cpp/blob/master/utils/image_io/src/image_io.cpp\r\n\r\n## Pitch\r\n\r\n## Alternatives\r\n\r\nFor reference this is the OpenCV to Libtorch conversion utils which I use, I would like something very similiar:\r\n```\r\nat::Tensor matToTensor(cv::Mat frame, int h, int w, int c) {\r\n    cv::cvtColor(frame, frame, CV_BGR2RGB);\r\n    frame.convertTo(frame, CV_32FC3, 1.0f / 255.0f);\r\n    auto input_tensor = torch::from_blob(frame.data, {1, h, w, c});\r\n    input_tensor = input_tensor.permute({0, 3, 1, 2});\r\n\r\n    torch::DeviceType device_type = torch::kCPU;\r\n//    if (torch::cuda::is_available()) {\r\n    device_type = torch::kCUDA;\r\n//    }\r\n    input_tensor = input_tensor.to(device_type);\r\n    return input_tensor;\r\n}\r\n\r\ncv::Mat tensorToOpenCv(at::Tensor out_tensor, int h, int w, int c) {\r\n    out_tensor = out_tensor.squeeze().detach().permute({1, 2, 0});\r\n    out_tensor = out_tensor.mul(255).clamp(0, 255).to(torch::kU8);\r\n    out_tensor = out_tensor.to(torch::kCPU);\r\n    cv::Mat resultImg(h, w, CV_8UC3);\r\n    // cv::Mat resultImg(h, w, CV_8UC1);\r\n    std::memcpy((void *) resultImg.data, out_tensor.data_ptr(), sizeof(torch::kU8) * out_tensor.numel());\r\n    return resultImg;\r\n}\r\n\r\n```\r\n## Additional context\r\n\r\nI found this:\"https://github.com/pytorch/vision/blob/5e4a9f6d1a2bf85137f4826dbf76e4f25986f878/torchvision/csrc/cpu/image/readpng_cpu.cpp\r\nhowever, could not get any useful method out of it. \r\n\r\nThanks, \r\n\r\n\r\n\r\n\n", "before_files": [{"content": "import torch\n\nimport os\nimport os.path as osp\nimport importlib.machinery\n\n_HAS_IMAGE_OPT = False\n\ntry:\n    lib_dir = osp.join(osp.dirname(__file__), \"..\")\n\n    loader_details = (\n        importlib.machinery.ExtensionFileLoader,\n        importlib.machinery.EXTENSION_SUFFIXES\n    )\n\n    extfinder = importlib.machinery.FileFinder(lib_dir, loader_details)  # type: ignore[arg-type]\n    ext_specs = extfinder.find_spec(\"image\")\n    if ext_specs is not None:\n        torch.ops.load_library(ext_specs.origin)\n        _HAS_IMAGE_OPT = True\nexcept (ImportError, OSError):\n    pass\n\n\ndef decode_png(input: torch.Tensor) -> torch.Tensor:\n    \"\"\"\n    Decodes a PNG image into a 3 dimensional RGB Tensor.\n    The values of the output tensor are uint8 between 0 and 255.\n\n    Arguments:\n        input (Tensor[1]): a one dimensional int8 tensor containing\n    the raw bytes of the PNG image.\n\n    Returns:\n        output (Tensor[3, image_height, image_width])\n    \"\"\"\n    if not isinstance(input, torch.Tensor) or input.numel() == 0 or input.ndim != 1:  # type: ignore[attr-defined]\n        raise ValueError(\"Expected a non empty 1-dimensional tensor.\")\n\n    if not input.dtype == torch.uint8:\n        raise ValueError(\"Expected a torch.uint8 tensor.\")\n    output = torch.ops.image.decode_png(input)\n    return output\n\n\ndef read_png(path: str) -> torch.Tensor:\n    \"\"\"\n    Reads a PNG image into a 3 dimensional RGB Tensor.\n    The values of the output tensor are uint8 between 0 and 255.\n\n    Arguments:\n        path (str): path of the PNG image.\n\n    Returns:\n        output (Tensor[3, image_height, image_width])\n    \"\"\"\n    if not os.path.isfile(path):\n        raise ValueError(\"Expected a valid file path.\")\n\n    size = os.path.getsize(path)\n    if size == 0:\n        raise ValueError(\"Expected a non empty file.\")\n    data = torch.from_file(path, dtype=torch.uint8, size=size)\n    return decode_png(data)\n\n\ndef decode_jpeg(input: torch.Tensor) -> torch.Tensor:\n    \"\"\"\n    Decodes a JPEG image into a 3 dimensional RGB Tensor.\n    The values of the output tensor are uint8 between 0 and 255.\n    Arguments:\n        input (Tensor[1]): a one dimensional int8 tensor containing\n    the raw bytes of the JPEG image.\n    Returns:\n        output (Tensor[3, image_height, image_width])\n    \"\"\"\n    if not isinstance(input, torch.Tensor) or len(input) == 0 or input.ndim != 1:  # type: ignore[attr-defined]\n        raise ValueError(\"Expected a non empty 1-dimensional tensor.\")\n\n    if not input.dtype == torch.uint8:\n        raise ValueError(\"Expected a torch.uint8 tensor.\")\n\n    output = torch.ops.image.decode_jpeg(input)\n    return output\n\n\ndef read_jpeg(path: str) -> torch.Tensor:\n    \"\"\"\n    Reads a JPEG image into a 3 dimensional RGB Tensor.\n    The values of the output tensor are uint8 between 0 and 255.\n    Arguments:\n        path (str): path of the JPEG image.\n    Returns:\n        output (Tensor[3, image_height, image_width])\n    \"\"\"\n    if not os.path.isfile(path):\n        raise ValueError(\"Expected a valid file path.\")\n\n    size = os.path.getsize(path)\n    if size == 0:\n        raise ValueError(\"Expected a non empty file.\")\n    data = torch.from_file(path, dtype=torch.uint8, size=size)\n    return decode_jpeg(data)\n", "path": "torchvision/io/image.py"}], "after_files": [{"content": "import torch\n\nimport os\nimport os.path as osp\nimport importlib.machinery\n\n_HAS_IMAGE_OPT = False\n\ntry:\n    lib_dir = osp.join(osp.dirname(__file__), \"..\")\n\n    loader_details = (\n        importlib.machinery.ExtensionFileLoader,\n        importlib.machinery.EXTENSION_SUFFIXES\n    )\n\n    extfinder = importlib.machinery.FileFinder(lib_dir, loader_details)  # type: ignore[arg-type]\n    ext_specs = extfinder.find_spec(\"image\")\n    if ext_specs is not None:\n        torch.ops.load_library(ext_specs.origin)\n        _HAS_IMAGE_OPT = True\nexcept (ImportError, OSError):\n    pass\n\n\ndef decode_png(input: torch.Tensor) -> torch.Tensor:\n    \"\"\"\n    Decodes a PNG image into a 3 dimensional RGB Tensor.\n    The values of the output tensor are uint8 between 0 and 255.\n\n    Arguments:\n        input (Tensor[1]): a one dimensional int8 tensor containing\n    the raw bytes of the PNG image.\n\n    Returns:\n        output (Tensor[3, image_height, image_width])\n    \"\"\"\n    if not isinstance(input, torch.Tensor) or input.numel() == 0 or input.ndim != 1:  # type: ignore[attr-defined]\n        raise ValueError(\"Expected a non empty 1-dimensional tensor.\")\n\n    if not input.dtype == torch.uint8:\n        raise ValueError(\"Expected a torch.uint8 tensor.\")\n    output = torch.ops.image.decode_png(input)\n    return output\n\n\ndef read_png(path: str) -> torch.Tensor:\n    \"\"\"\n    Reads a PNG image into a 3 dimensional RGB Tensor.\n    The values of the output tensor are uint8 between 0 and 255.\n\n    Arguments:\n        path (str): path of the PNG image.\n\n    Returns:\n        output (Tensor[3, image_height, image_width])\n    \"\"\"\n    if not os.path.isfile(path):\n        raise ValueError(\"Expected a valid file path.\")\n\n    size = os.path.getsize(path)\n    if size == 0:\n        raise ValueError(\"Expected a non empty file.\")\n    data = torch.from_file(path, dtype=torch.uint8, size=size)\n    return decode_png(data)\n\n\ndef decode_jpeg(input: torch.Tensor) -> torch.Tensor:\n    \"\"\"\n    Decodes a JPEG image into a 3 dimensional RGB Tensor.\n    The values of the output tensor are uint8 between 0 and 255.\n    Arguments:\n        input (Tensor[1]): a one dimensional int8 tensor containing\n    the raw bytes of the JPEG image.\n    Returns:\n        output (Tensor[3, image_height, image_width])\n    \"\"\"\n    if not isinstance(input, torch.Tensor) or len(input) == 0 or input.ndim != 1:  # type: ignore[attr-defined]\n        raise ValueError(\"Expected a non empty 1-dimensional tensor.\")\n\n    if not input.dtype == torch.uint8:\n        raise ValueError(\"Expected a torch.uint8 tensor.\")\n\n    output = torch.ops.image.decode_jpeg(input)\n    return output\n\n\ndef read_jpeg(path: str) -> torch.Tensor:\n    \"\"\"\n    Reads a JPEG image into a 3 dimensional RGB Tensor.\n    The values of the output tensor are uint8 between 0 and 255.\n    Arguments:\n        path (str): path of the JPEG image.\n    Returns:\n        output (Tensor[3, image_height, image_width])\n    \"\"\"\n    if not os.path.isfile(path):\n        raise ValueError(\"Expected a valid file path.\")\n\n    size = os.path.getsize(path)\n    if size == 0:\n        raise ValueError(\"Expected a non empty file.\")\n    data = torch.from_file(path, dtype=torch.uint8, size=size)\n    return decode_jpeg(data)\n\n\ndef encode_jpeg(input: torch.Tensor, quality: int = 75) -> torch.Tensor:\n    \"\"\"\n    Takes an input tensor in CHW layout (or HW in the case of grayscale images)\n    and returns a buffer with the contents of its corresponding JPEG file.\n    Arguments:\n        input (Tensor[channels, image_height, image_width]): int8 image tensor\n    of `c` channels, where `c` must be 1 or 3.\n        quality (int): Quality of the resulting JPEG file, it must be a number\n    between 1 and 100. Default: 75\n    Returns\n        output (Tensor[1]): A one dimensional int8 tensor that contains the raw\n    bytes of the JPEG file.\n    \"\"\"\n    if quality < 1 or quality > 100:\n        raise ValueError('Image quality should be a positive number '\n                         'between 1 and 100')\n\n    output = torch.ops.image.encode_jpeg(input, quality)\n    return output\n\n\ndef write_jpeg(input: torch.Tensor, filename: str, quality: int = 75):\n    \"\"\"\n    Takes an input tensor in CHW layout (or HW in the case of grayscale images)\n    and saves it in a JPEG file.\n    Arguments:\n        input (Tensor[channels, image_height, image_width]): int8 image tensor\n    of `c` channels, where `c` must be 1 or 3.\n        filename (str): Path to save the image.\n        quality (int): Quality of the resulting JPEG file, it must be a number\n    between 1 and 100. Default: 75\n    \"\"\"\n    if quality < 1 or quality > 100:\n        raise ValueError('Image quality should be a positive number '\n                         'between 1 and 100')\n\n    torch.ops.image.write_jpeg(input, filename, quality)\n", "path": "torchvision/io/image.py"}]}