diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp
index ebeb4ce1bf..c8dc7b62e2 100644
--- a/esphome/components/display/display_buffer.cpp
+++ b/esphome/components/display/display_buffer.cpp
@@ -325,63 +325,8 @@ void DisplayBuffer::vprintf_(int x, int y, Font *font, Color color, TextAlign al
     this->print(x, y, font, color, align, buffer);
 }
 
-void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color color_off) {
-  bool transparent = image->has_transparency();
-
-  switch (image->get_type()) {
-    case IMAGE_TYPE_BINARY: {
-      for (int img_x = 0; img_x < image->get_width(); img_x++) {
-        for (int img_y = 0; img_y < image->get_height(); img_y++) {
-          if (image->get_pixel(img_x, img_y)) {
-            this->draw_pixel_at(x + img_x, y + img_y, color_on);
-          } else if (!transparent) {
-            this->draw_pixel_at(x + img_x, y + img_y, color_off);
-          }
-        }
-      }
-      break;
-    }
-    case IMAGE_TYPE_GRAYSCALE:
-      for (int img_x = 0; img_x < image->get_width(); img_x++) {
-        for (int img_y = 0; img_y < image->get_height(); img_y++) {
-          auto color = image->get_grayscale_pixel(img_x, img_y);
-          if (color.w >= 0x80) {
-            this->draw_pixel_at(x + img_x, y + img_y, color);
-          }
-        }
-      }
-      break;
-    case IMAGE_TYPE_RGB565:
-      for (int img_x = 0; img_x < image->get_width(); img_x++) {
-        for (int img_y = 0; img_y < image->get_height(); img_y++) {
-          auto color = image->get_rgb565_pixel(img_x, img_y);
-          if (color.w >= 0x80) {
-            this->draw_pixel_at(x + img_x, y + img_y, color);
-          }
-        }
-      }
-      break;
-    case IMAGE_TYPE_RGB24:
-      for (int img_x = 0; img_x < image->get_width(); img_x++) {
-        for (int img_y = 0; img_y < image->get_height(); img_y++) {
-          auto color = image->get_color_pixel(img_x, img_y);
-          if (color.w >= 0x80) {
-            this->draw_pixel_at(x + img_x, y + img_y, color);
-          }
-        }
-      }
-      break;
-    case IMAGE_TYPE_RGBA:
-      for (int img_x = 0; img_x < image->get_width(); img_x++) {
-        for (int img_y = 0; img_y < image->get_height(); img_y++) {
-          auto color = image->get_rgba_pixel(img_x, img_y);
-          if (color.w >= 0x80) {
-            this->draw_pixel_at(x + img_x, y + img_y, color);
-          }
-        }
-      }
-      break;
-  }
+void DisplayBuffer::image(int x, int y, BaseImage *image, Color color_on, Color color_off) {
+  image->draw(x, y, this, color_on, color_off);
 }
 
 #ifdef USE_GRAPH
@@ -656,23 +601,91 @@ Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : basel
     glyphs_.emplace_back(&data[i]);
 }
 
-bool Image::get_pixel(int x, int y) const {
+void Image::draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) {
+  switch (type_) {
+    case IMAGE_TYPE_BINARY: {
+      for (int img_x = 0; img_x < width_; img_x++) {
+        for (int img_y = 0; img_y < height_; img_y++) {
+          if (this->get_binary_pixel_(img_x, img_y)) {
+            display->draw_pixel_at(x + img_x, y + img_y, color_on);
+          } else if (!this->transparent_) {
+            display->draw_pixel_at(x + img_x, y + img_y, color_off);
+          }
+        }
+      }
+      break;
+    }
+    case IMAGE_TYPE_GRAYSCALE:
+      for (int img_x = 0; img_x < width_; img_x++) {
+        for (int img_y = 0; img_y < height_; img_y++) {
+          auto color = this->get_grayscale_pixel_(img_x, img_y);
+          if (color.w >= 0x80) {
+            display->draw_pixel_at(x + img_x, y + img_y, color);
+          }
+        }
+      }
+      break;
+    case IMAGE_TYPE_RGB565:
+      for (int img_x = 0; img_x < width_; img_x++) {
+        for (int img_y = 0; img_y < height_; img_y++) {
+          auto color = this->get_rgb565_pixel_(img_x, img_y);
+          if (color.w >= 0x80) {
+            display->draw_pixel_at(x + img_x, y + img_y, color);
+          }
+        }
+      }
+      break;
+    case IMAGE_TYPE_RGB24:
+      for (int img_x = 0; img_x < width_; img_x++) {
+        for (int img_y = 0; img_y < height_; img_y++) {
+          auto color = this->get_rgb24_pixel_(img_x, img_y);
+          if (color.w >= 0x80) {
+            display->draw_pixel_at(x + img_x, y + img_y, color);
+          }
+        }
+      }
+      break;
+    case IMAGE_TYPE_RGBA:
+      for (int img_x = 0; img_x < width_; img_x++) {
+        for (int img_y = 0; img_y < height_; img_y++) {
+          auto color = this->get_rgba_pixel_(img_x, img_y);
+          if (color.w >= 0x80) {
+            display->draw_pixel_at(x + img_x, y + img_y, color);
+          }
+        }
+      }
+      break;
+  }
+}
+Color Image::get_pixel(int x, int y, Color color_on, Color color_off) const {
   if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
-    return false;
+    return color_off;
+  switch (this->type_) {
+    case IMAGE_TYPE_BINARY:
+      return this->get_binary_pixel_(x, y) ? color_on : color_off;
+    case IMAGE_TYPE_GRAYSCALE:
+      return this->get_grayscale_pixel_(x, y);
+    case IMAGE_TYPE_RGB565:
+      return this->get_rgb565_pixel_(x, y);
+    case IMAGE_TYPE_RGB24:
+      return this->get_rgb24_pixel_(x, y);
+    case IMAGE_TYPE_RGBA:
+      return this->get_rgba_pixel_(x, y);
+    default:
+      return color_off;
+  }
+}
+bool Image::get_binary_pixel_(int x, int y) const {
   const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
   const uint32_t pos = x + y * width_8;
   return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
 }
-Color Image::get_rgba_pixel(int x, int y) const {
-  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
-    return Color::BLACK;
+Color Image::get_rgba_pixel_(int x, int y) const {
   const uint32_t pos = (x + y * this->width_) * 4;
   return Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1),
                progmem_read_byte(this->data_start_ + pos + 2), progmem_read_byte(this->data_start_ + pos + 3));
 }
-Color Image::get_color_pixel(int x, int y) const {
-  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
-    return Color::BLACK;
+Color Image::get_rgb24_pixel_(int x, int y) const {
   const uint32_t pos = (x + y * this->width_) * 3;
   Color color = Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1),
                       progmem_read_byte(this->data_start_ + pos + 2));
@@ -685,9 +698,7 @@ Color Image::get_color_pixel(int x, int y) const {
   }
   return color;
 }
-Color Image::get_rgb565_pixel(int x, int y) const {
-  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
-    return Color::BLACK;
+Color Image::get_rgb565_pixel_(int x, int y) const {
   const uint32_t pos = (x + y * this->width_) * 2;
   uint16_t rgb565 =
       progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1);
@@ -703,9 +714,7 @@ Color Image::get_rgb565_pixel(int x, int y) const {
   }
   return color;
 }
-Color Image::get_grayscale_pixel(int x, int y) const {
-  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
-    return Color::BLACK;
+Color Image::get_grayscale_pixel_(int x, int y) const {
   const uint32_t pos = (x + y * this->width_);
   const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
   uint8_t alpha = (gray == 1 && transparent_) ? 0 : 0xFF;
@@ -716,7 +725,6 @@ int Image::get_height() const { return this->height_; }
 ImageType Image::get_type() const { return this->type_; }
 Image::Image(const uint8_t *data_start, int width, int height, ImageType type)
     : width_(width), height_(height), type_(type), data_start_(data_start) {}
-int Image::get_current_frame() const { return 0; }
 
 Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type)
     : Image(data_start, width, height, type),
diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h
index 33e74b8df4..0c31ac24d9 100644
--- a/esphome/components/display/display_buffer.h
+++ b/esphome/components/display/display_buffer.h
@@ -123,8 +123,8 @@ class Rect {
   void info(const std::string &prefix = "rect info:");
 };
 
+class BaseImage;
 class Font;
-class Image;
 class DisplayBuffer;
 class DisplayPage;
 class DisplayOnPageChangeTrigger;
@@ -315,7 +315,7 @@ class DisplayBuffer {
    * @param color_on The color to replace in binary images for the on bits.
    * @param color_off The color to replace in binary images for the off bits.
    */
-  void image(int x, int y, Image *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
+  void image(int x, int y, BaseImage *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
 
 #ifdef USE_GRAPH
   /** Draw the `graph` with the top-left corner at [x,y] to the screen.
@@ -529,24 +529,33 @@ class Font {
   int height_;
 };
 
-class Image {
+class BaseImage {
+ public:
+  virtual void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) = 0;
+  virtual int get_width() const = 0;
+  virtual int get_height() const = 0;
+};
+
+class Image : public BaseImage {
  public:
   Image(const uint8_t *data_start, int width, int height, ImageType type);
-  bool get_pixel(int x, int y) const;
-  Color get_color_pixel(int x, int y) const;
-  Color get_rgba_pixel(int x, int y) const;
-  Color get_rgb565_pixel(int x, int y) const;
-  Color get_grayscale_pixel(int x, int y) const;
-  int get_width() const;
-  int get_height() const;
+  Color get_pixel(int x, int y, Color color_on = COLOR_ON, Color color_off = COLOR_OFF) const;
+  int get_width() const override;
+  int get_height() const override;
   ImageType get_type() const;
 
-  virtual int get_current_frame() const;
+  void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) override;
 
   void set_transparency(bool transparent) { transparent_ = transparent; }
   bool has_transparency() const { return transparent_; }
 
  protected:
+  bool get_binary_pixel_(int x, int y) const;
+  Color get_rgb24_pixel_(int x, int y) const;
+  Color get_rgba_pixel_(int x, int y) const;
+  Color get_rgb565_pixel_(int x, int y) const;
+  Color get_grayscale_pixel_(int x, int y) const;
+
   int width_;
   int height_;
   ImageType type_;
@@ -559,7 +568,7 @@ class Animation : public Image {
   Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type);
 
   uint32_t get_animation_frame_count() const;
-  int get_current_frame() const override;
+  int get_current_frame() const;
   void next_frame();
   void prev_frame();